switched api to fastapi, simplified setup
|
|
@ -6,3 +6,4 @@ gunicorn.log
|
|||
config_mysql.py
|
||||
hugo.exe
|
||||
spectre/
|
||||
velconnect_backup.sql
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
## VELConnect API Setup
|
||||
|
||||
1. `cd velconnect`
|
||||
2. Create pip env: `python3 -m venv env`
|
||||
3. Activate the env `. env/bin/activate`
|
||||
4. Install packages `pip install -r requirements.txt`
|
||||
5. Run `./run_server.sh`
|
||||
- Or set up systemctl service:
|
||||
```ini
|
||||
[Unit]
|
||||
Description=VelNet Logging
|
||||
Requires=network.target
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=ubuntu
|
||||
Group=ubuntu
|
||||
Environment="PATH=/home/ubuntu/VEL-Connect/velconnect/env/bin"
|
||||
WorkingDirectory=/home/ubuntu/VEL-Connect/velconnect
|
||||
ExecStart=/home/ubuntu/VEL-Connect/velconnect/env/bin/uvicorn --port 8005 main:app
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
```
|
||||
- Enter the above in `/etc/systemd/system/velconnect.service`
|
||||
- `sudo systemctl enable velconnect.service`
|
||||
- `sudo systemctl start velconnect.service`
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
flask
|
||||
simplejson
|
||||
Flask-Limiter
|
||||
pymysql
|
||||
gunicorn
|
||||
flask-cors
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
from flask import Flask, jsonify, make_response, request
|
||||
from flask_cors import CORS
|
||||
from velconnect.auth import limiter
|
||||
from velconnect.logger import logger
|
||||
from time import strftime
|
||||
import traceback
|
||||
|
||||
def create_app():
|
||||
app = Flask(
|
||||
__name__,
|
||||
instance_relative_config=False,
|
||||
)
|
||||
app.config.from_pyfile('config.py')
|
||||
|
||||
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})
|
||||
|
||||
limiter.init_app(app)
|
||||
|
||||
from .routes import api
|
||||
app.register_blueprint(api.bp, url_prefix='/api')
|
||||
|
||||
from .routes import website
|
||||
app.register_blueprint(website.bp)
|
||||
|
||||
# Error handlers
|
||||
app.register_error_handler(404, resource_not_found)
|
||||
app.register_error_handler(401, noapikey_handler)
|
||||
app.register_error_handler(429, ratelimit_handler)
|
||||
app.register_error_handler(Exception, exceptions)
|
||||
|
||||
app.after_request(after_request)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
# @app.after_request
|
||||
def after_request(response):
|
||||
""" Logging after every request. """
|
||||
# This avoids the duplication of registry in the log,
|
||||
# since that 500 is already logged via @app.errorhandler.
|
||||
if response.status_code != 500:
|
||||
ts = strftime('[%Y-%b-%d %H:%M]')
|
||||
logger.error('%s %s %s %s %s %s',
|
||||
ts,
|
||||
request.remote_addr,
|
||||
request.method,
|
||||
request.scheme,
|
||||
request.full_path,
|
||||
response.status)
|
||||
return response
|
||||
|
||||
|
||||
# @app.errorhandler(Exception)
|
||||
def exceptions(e):
|
||||
""" Logging after every Exception. """
|
||||
ts = strftime('[%Y-%b-%d %H:%M]')
|
||||
tb = traceback.format_exc()
|
||||
logger.error('%s %s %s %s %s 5xx INTERNAL SERVER ERROR\n%s',
|
||||
ts,
|
||||
request.remote_addr,
|
||||
request.method,
|
||||
request.scheme,
|
||||
request.full_path,
|
||||
tb)
|
||||
|
||||
return "SERVER ERROR", 500
|
||||
|
||||
|
||||
# @app.errorhandler(429)
|
||||
def ratelimit_handler(e):
|
||||
return make_response(
|
||||
jsonify(error="ratelimit exceeded %s" % e.description), 429
|
||||
)
|
||||
|
||||
|
||||
def resource_not_found(e):
|
||||
return jsonify(error=str(e)), 404
|
||||
|
||||
|
||||
# @app.errorhandler(401)
|
||||
def noapikey_handler(e):
|
||||
return make_response(
|
||||
jsonify(error="not authorized %s" % e.description), 401
|
||||
)
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
from fastapi import APIRouter
|
||||
from fastapi import Query
|
||||
from typing import Optional
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi import FastAPI, Body, Depends, HTTPException, status
|
||||
from fastapi.security import OAuth2PasswordBearer
|
||||
from db import query, insert
|
||||
from pydantic import BaseModel
|
||||
from typing import Union
|
||||
|
||||
|
||||
# APIRouter creates path operations for user module
|
||||
router = APIRouter(
|
||||
prefix="/api",
|
||||
tags=["API"],
|
||||
responses={404: {"description": "Not found"}},
|
||||
)
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(
|
||||
tokenUrl="token") # use token authentication
|
||||
|
||||
|
||||
def api_key_auth(api_key: str = Depends(oauth2_scheme)):
|
||||
return True
|
||||
values = query(
|
||||
"SELECT * FROM `APIKey` WHERE `key`=%(key)s;", {'key': api_key})
|
||||
if not (len(values) > 0 and values['auth_level'] < 0):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Forbidden"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||
async def read_root():
|
||||
return """
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
|
||||
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
||||
<title>API Reference</title>
|
||||
</head>
|
||||
<body>
|
||||
<rapi-doc
|
||||
render-style = "read"
|
||||
primary-color = "#bc1f2d"
|
||||
show-header = "false"
|
||||
show-info = "true"
|
||||
spec-url = "/openapi.json"
|
||||
default-schema-tab = 'example'
|
||||
>
|
||||
<div slot="nav-logo" style="display: flex; align-items: center; justify-content: center;">
|
||||
<img src = "http://velconnect.ugavel.com/static/favicons/android-chrome-256x256.png" style="width:10em; margin: auto;" />
|
||||
</div>
|
||||
</rapi-doc>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@router.get('/get_all_headsets')
|
||||
def get_all_headsets():
|
||||
"""Returns a list of all headsets and details associated with them."""
|
||||
values = query("SELECT * FROM `Headset`;")
|
||||
return values
|
||||
|
||||
|
||||
@router.get('/pair_headset/{pairing_code}')
|
||||
def pair_headset(pairing_code: str):
|
||||
values = query("SELECT * FROM `Headset` WHERE `pairing_code`=%(pairing_code)s;",
|
||||
{'pairing_code': pairing_code})
|
||||
if len(values) == 1:
|
||||
print(values[0]['hw_id'])
|
||||
return {'hw_id': values[0]['hw_id']}
|
||||
return {'error': 'Not found'}, 400
|
||||
|
||||
|
||||
class UpdatePairingCode(BaseModel):
|
||||
hw_id: str
|
||||
pairing_code: str
|
||||
|
||||
|
||||
@router.post('/update_pairing_code')
|
||||
def update_paring_code(data: UpdatePairingCode):
|
||||
"""This also creates a headset if it doesn't exist"""
|
||||
|
||||
if 'hw_id' not in data:
|
||||
return 'Must supply hw_id', 400
|
||||
if 'pairing_code' not in data:
|
||||
return 'Must supply pairing_code', 400
|
||||
|
||||
insert("""
|
||||
INSERT INTO `Headset`(
|
||||
`hw_id`,
|
||||
`pairing_code`,
|
||||
`last_used`
|
||||
) VALUES (
|
||||
%(hw_id)s,
|
||||
%(pairing_code)s,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
pairing_code=%(pairing_code)s,
|
||||
last_used=CURRENT_TIMESTAMP;
|
||||
""", data)
|
||||
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@router.get('/get_state/{hw_id}')
|
||||
def get_headset_details(hw_id: str):
|
||||
data = get_headset_details_db(hw_id)
|
||||
if data is None:
|
||||
return {'error': "Can't find headset with that id."}
|
||||
else:
|
||||
return data
|
||||
|
||||
|
||||
def get_headset_details_db(hw_id):
|
||||
headsets = query("""
|
||||
SELECT * FROM `Headset` WHERE `hw_id`=%(hw_id)s;
|
||||
""", {'hw_id': hw_id})
|
||||
if len(headsets) == 0:
|
||||
return None
|
||||
|
||||
room = get_room_details_db(headsets[0]['current_room'])
|
||||
|
||||
return {'user': headsets[0], 'room': room}
|
||||
|
||||
|
||||
@router.post('/set_headset_details/{hw_id}')
|
||||
def set_headset_details_generic(hw_id: str, data: dict):
|
||||
print(data)
|
||||
|
||||
allowed_keys = [
|
||||
'current_room',
|
||||
'pairing_code',
|
||||
'user_color',
|
||||
'user_name',
|
||||
'avatar_url',
|
||||
'user_details',
|
||||
]
|
||||
for key in data:
|
||||
if key in allowed_keys:
|
||||
if key == 'current_room':
|
||||
create_room(data['current_room'])
|
||||
insert("UPDATE `Headset` SET " + key +
|
||||
"=%(value)s, modified_by=%(sender_id)s WHERE `hw_id`=%(hw_id)s;", {'value': data[key], 'hw_id': hw_id, 'sender_id': data['sender_id']})
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@router.post('/set_room_details/{room_id}')
|
||||
def set_room_details_generic(room_id: str, data: dict):
|
||||
print(data)
|
||||
allowed_keys = [
|
||||
'modified_by',
|
||||
'whitelist',
|
||||
'tv_url',
|
||||
'carpet_color',
|
||||
'room_details',
|
||||
]
|
||||
|
||||
for key in data:
|
||||
if key in allowed_keys:
|
||||
insert("UPDATE `Room` SET " + key +
|
||||
"=%(value)s, modified_by=%(sender_id)s WHERE `room_id`=%(room_id)s;", {'value': data[key], 'room_id': room_id, 'sender_id': data['sender_id']})
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@router.get('/get_room_details/{room_id}')
|
||||
def get_room_details(room_id: str):
|
||||
return get_room_details_db(room_id)
|
||||
|
||||
|
||||
def get_room_details_db(room_id):
|
||||
values = query("""
|
||||
SELECT * FROM `Room` WHERE room_id=%(room_id)s;
|
||||
""", {'room_id': room_id})
|
||||
if len(values) == 1:
|
||||
return values[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def create_room(room_id):
|
||||
insert("""
|
||||
INSERT IGNORE INTO `Room`(room_id)
|
||||
VALUES(
|
||||
%(room_id)s
|
||||
);
|
||||
""", {'room_id': room_id})
|
||||
return {'room_id': room_id}
|
||||
|
||||
|
||||
@router.post('/update_user_count', tags=["User Count"])
|
||||
def update_user_count(data: dict):
|
||||
insert("""
|
||||
REPLACE INTO `UserCount`
|
||||
VALUES(
|
||||
CURRENT_TIMESTAMP,
|
||||
%(hw_id)s,
|
||||
%(room_id)s,
|
||||
%(total_users)s,
|
||||
%(room_users)s,
|
||||
%(version)s,
|
||||
%(platform)s
|
||||
);
|
||||
""", data)
|
||||
return {'success': True}
|
||||
|
||||
|
||||
@router.get('/get_user_count', tags=["User Count"])
|
||||
def get_user_count(hours: float = 24):
|
||||
values = query("""
|
||||
SELECT timestamp, total_users
|
||||
FROM `UserCount`
|
||||
WHERE TIMESTAMP > DATE_SUB(NOW(), INTERVAL """ + str(hours) + """ HOUR);
|
||||
""")
|
||||
return values
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
from functools import wraps
|
||||
import inspect
|
||||
from flask import abort, request, make_response, jsonify
|
||||
from velconnect.db import connectToDB
|
||||
from flask_limiter import Limiter
|
||||
from flask_limiter.util import get_remote_address
|
||||
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
|
||||
def require_api_key(level):
|
||||
def decorator(view_function):
|
||||
def wrapper(*args, **kwargs):
|
||||
return view_function(*args, **kwargs)
|
||||
key = request.headers.get('x-api-key')
|
||||
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT * FROM `APIKey` WHERE `key`=%(key)s;
|
||||
"""
|
||||
curr.execute(query, {'key': key})
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
if len(values) > 0 and values['auth_level'] < level:
|
||||
return view_function(*args, **kwargs)
|
||||
else:
|
||||
abort(401)
|
||||
wrapper.__name__ = view_function.__name__
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def required_args(f):
|
||||
@wraps(f)
|
||||
def decorated_function(*args, **kwargs):
|
||||
""" Decorator that makes sure the view arguments are in the request args, otherwise 400 error """
|
||||
sig = inspect.signature(f)
|
||||
data = request.args
|
||||
|
||||
for arg in sig.parameters.values():
|
||||
# Check if the argument is passed from the url
|
||||
if arg.name in kwargs:
|
||||
continue
|
||||
# check if the argument is in the json data
|
||||
if data and data.get(arg.name) is not None:
|
||||
kwargs[arg.name] = data.get(arg.name)
|
||||
# else check if it has been given a default
|
||||
elif arg.default is not arg.empty:
|
||||
kwargs[arg.name] = arg.default
|
||||
|
||||
missing_args = [arg for arg in sig.parameters.keys()
|
||||
if arg not in kwargs.keys()]
|
||||
if missing_args:
|
||||
return 'Did not receive args for: {}'.format(', '.join(missing_args)), 400
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated_function
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
# Flask-Caching related configs
|
||||
CACHE_TYPE = "SimpleCache"
|
||||
CACHE_DEFAULT_TIMEOUT = 3600
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# from config import *
|
||||
import pymysql
|
||||
from pymysql import converters
|
||||
from velconnect.config_mysql import *
|
||||
from config_mysql import *
|
||||
|
||||
|
||||
def connectToDB():
|
||||
|
|
@ -14,8 +14,34 @@ def connectToDB():
|
|||
db=MYSQL_DATABASE_DB,
|
||||
cursorclass=pymysql.cursors.DictCursor,
|
||||
conv=conv,
|
||||
ssl={"fake_flag_to_enable_tls":True},
|
||||
ssl={"fake_flag_to_enable_tls": True},
|
||||
)
|
||||
curr = conn.cursor()
|
||||
|
||||
return conn, curr
|
||||
|
||||
|
||||
def query(query: str, data: dict = None) -> list:
|
||||
try:
|
||||
conn, curr = connectToDB()
|
||||
curr.execute(query, data)
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
return values
|
||||
except Exception:
|
||||
print(curr._last_executed)
|
||||
curr.close()
|
||||
raise
|
||||
|
||||
|
||||
def insert(query: str, data: dict = None) -> bool:
|
||||
try:
|
||||
conn, curr = connectToDB()
|
||||
curr.execute(query, data)
|
||||
conn.commit()
|
||||
curr.close()
|
||||
return True
|
||||
except Exception:
|
||||
print(curr._last_executed)
|
||||
curr.close()
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -1,7 +0,0 @@
|
|||
import logging
|
||||
from logging.handlers import RotatingFileHandler
|
||||
|
||||
handler = RotatingFileHandler('app.log', maxBytes=100000000, backupCount=1500)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.ERROR)
|
||||
logger.addHandler(handler)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from imp import reload
|
||||
import uvicorn
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi import FastAPI
|
||||
from api import router as api_router
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
origins = [
|
||||
"http://velconnect.ugavel.com",
|
||||
"https://velconnect.ugavel.com",
|
||||
"http://localhost",
|
||||
"http://localhost:8080",
|
||||
]
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(api_router)
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run("main:app", host='127.0.0.1', port=8005,
|
||||
log_level="info", reload=True)
|
||||
print("running")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
fastapi
|
||||
autopep8
|
||||
uvicorn
|
||||
pymysql
|
||||
|
|
@ -1,274 +0,0 @@
|
|||
from flask.helpers import send_from_directory
|
||||
from velconnect.auth import require_api_key
|
||||
from velconnect.db import connectToDB
|
||||
from velconnect.logger import logger
|
||||
from flask import Blueprint, request, jsonify, render_template, url_for
|
||||
import time
|
||||
import simplejson as json
|
||||
from random import random
|
||||
|
||||
bp = Blueprint('api', __name__)
|
||||
|
||||
|
||||
@bp.route('/', methods=['GET'])
|
||||
def index():
|
||||
return render_template('api.html')
|
||||
|
||||
|
||||
@bp.route('/api_spec.json', methods=['GET'])
|
||||
@require_api_key(0)
|
||||
def api_spec():
|
||||
response = send_from_directory('static', 'api_spec.json')
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/get_all_headsets', methods=['GET'])
|
||||
@require_api_key(0)
|
||||
def get_all_headsets():
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT * FROM `Headset`;
|
||||
"""
|
||||
curr.execute(query, None)
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
response = jsonify(values)
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/pair_headset/<pairing_code>', methods=['GET'])
|
||||
@require_api_key(0)
|
||||
def pair_headset(pairing_code):
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT * FROM `Headset` WHERE `pairing_code`=%(pairing_code)s;
|
||||
"""
|
||||
curr.execute(query, {'pairing_code': pairing_code})
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
if len(values) == 1:
|
||||
print(values[0]['hw_id'])
|
||||
response = jsonify({'hw_id': values[0]['hw_id']})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
return 'Not found', 400
|
||||
|
||||
|
||||
# This also creates a headset if it doesn't exist
|
||||
@bp.route('/update_pairing_code', methods=['POST'])
|
||||
@require_api_key(0)
|
||||
def update_paring_code():
|
||||
|
||||
data = request.json
|
||||
if 'hw_id' not in data:
|
||||
return 'Must supply hw_id', 400
|
||||
if 'pairing_code' not in data:
|
||||
return 'Must supply pairing_code', 400
|
||||
|
||||
conn, curr = connectToDB()
|
||||
|
||||
query = """
|
||||
INSERT INTO `Headset`(
|
||||
`hw_id`,
|
||||
`pairing_code`,
|
||||
`last_used`
|
||||
) VALUES (
|
||||
%(hw_id)s,
|
||||
%(pairing_code)s,
|
||||
CURRENT_TIMESTAMP
|
||||
)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
pairing_code=%(pairing_code)s,
|
||||
last_used=CURRENT_TIMESTAMP;
|
||||
"""
|
||||
curr.execute(query, data)
|
||||
conn.commit()
|
||||
curr.close()
|
||||
|
||||
response = jsonify({'success': True})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/get_state/<hw_id>', methods=['GET'])
|
||||
@require_api_key(10)
|
||||
def get_headset_details(hw_id):
|
||||
data = get_headset_details_db(hw_id)
|
||||
if data is None:
|
||||
response = jsonify({'error': "Can't find headset with that id."})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
else:
|
||||
response = jsonify(data)
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
def get_headset_details_db(hw_id):
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT * FROM `Headset` WHERE `hw_id`=%(hw_id)s;
|
||||
"""
|
||||
curr.execute(query, {'hw_id': hw_id})
|
||||
headsets = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
if len(headsets) == 0:
|
||||
return None
|
||||
|
||||
room = get_room_details_db(headsets[0]['current_room'])
|
||||
|
||||
return {'user': headsets[0], 'room': room}
|
||||
|
||||
|
||||
@bp.route('/set_headset_details/<hw_id>', methods=['POST'])
|
||||
@require_api_key(10)
|
||||
def set_headset_details_generic(hw_id):
|
||||
data = request.json
|
||||
logger.error(data)
|
||||
conn, curr = connectToDB()
|
||||
|
||||
allowed_keys = [
|
||||
'current_room',
|
||||
'pairing_code',
|
||||
'user_color',
|
||||
'user_name',
|
||||
'avatar_url',
|
||||
'user_details',
|
||||
]
|
||||
try:
|
||||
for key in data:
|
||||
if key in allowed_keys:
|
||||
if key == 'current_room':
|
||||
create_room(data['current_room'])
|
||||
query = "UPDATE `Headset` SET " + key + \
|
||||
"=%(value)s, modified_by=%(sender_id)s WHERE `hw_id`=%(hw_id)s;"
|
||||
curr.execute(
|
||||
query, {'value': data[key], 'hw_id': hw_id, 'sender_id': data['sender_id']})
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
logger.error(curr._last_executed)
|
||||
curr.close()
|
||||
logger.error(e)
|
||||
return 'Error', 400
|
||||
|
||||
curr.close()
|
||||
|
||||
response = jsonify({'success': True})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/set_room_details/<room_id>', methods=['POST'])
|
||||
@require_api_key(10)
|
||||
def set_room_details_generic(room_id):
|
||||
data = request.json
|
||||
logger.error(data)
|
||||
conn, curr = connectToDB()
|
||||
|
||||
allowed_keys = [
|
||||
'modified_by',
|
||||
'whitelist',
|
||||
'tv_url',
|
||||
'carpet_color',
|
||||
'room_details',
|
||||
]
|
||||
try:
|
||||
for key in data:
|
||||
if key in allowed_keys:
|
||||
query = "UPDATE `Room` SET " + key + \
|
||||
"=%(value)s, modified_by=%(sender_id)s WHERE `room_id`=%(room_id)s;"
|
||||
curr.execute(
|
||||
query, {'value': data[key], 'room_id': room_id, 'sender_id': data['sender_id']})
|
||||
conn.commit()
|
||||
except Exception as e:
|
||||
logger.error(curr._last_executed)
|
||||
curr.close()
|
||||
logger.error(e)
|
||||
return 'Error', 400
|
||||
|
||||
curr.close()
|
||||
|
||||
response = jsonify({'success': True})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/get_room_details/<room_id>', methods=['GET'])
|
||||
@require_api_key(10)
|
||||
def get_room_details(room_id):
|
||||
response = jsonify(get_room_details_db(room_id))
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
def get_room_details_db(room_id):
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT * FROM `Room` WHERE room_id=%(room_id)s;
|
||||
"""
|
||||
curr.execute(query, {'room_id': room_id})
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
if len(values) == 1:
|
||||
return values[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def create_room(room_id):
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
INSERT IGNORE INTO `Room`(room_id)
|
||||
VALUES(
|
||||
%(room_id)s
|
||||
);
|
||||
"""
|
||||
curr.execute(query, {'room_id': room_id})
|
||||
conn.commit()
|
||||
curr.close()
|
||||
return {'room_id': room_id}
|
||||
|
||||
|
||||
@bp.route('/update_user_count', methods=['POST'])
|
||||
@require_api_key(10)
|
||||
def update_user_count():
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
INSERT INTO `UserCount`
|
||||
VALUES(
|
||||
CURRENT_TIMESTAMP,
|
||||
%(hw_id)s,
|
||||
%(room_id)s,
|
||||
%(total_users)s,
|
||||
%(room_users)s,
|
||||
%(version)s,
|
||||
%(platform)s
|
||||
);
|
||||
"""
|
||||
data = request.json
|
||||
curr.execute(query, data)
|
||||
conn.commit()
|
||||
curr.close()
|
||||
response = jsonify({'success': True})
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
||||
|
||||
@bp.route('/get_user_count', methods=['GET'])
|
||||
def get_user_count():
|
||||
hours = request.args.get('hours', 24)
|
||||
conn, curr = connectToDB()
|
||||
query = """
|
||||
SELECT timestamp, total_users
|
||||
FROM `UserCount`
|
||||
WHERE TIMESTAMP > DATE_SUB(NOW(), INTERVAL """ + str(hours) + """ HOUR);
|
||||
"""
|
||||
data = request.json
|
||||
curr.execute(query, data)
|
||||
values = [dict(row) for row in curr.fetchall()]
|
||||
curr.close()
|
||||
response = jsonify(values)
|
||||
response.headers.add('Access-Control-Allow-Origin', '*')
|
||||
return response
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
from velconnect.db import connectToDB
|
||||
from flask import Blueprint, request, jsonify, render_template
|
||||
from velconnect.logger import logger
|
||||
import time
|
||||
import simplejson as json
|
||||
|
||||
bp = Blueprint('website', __name__, template_folder='templates')
|
||||
|
||||
|
||||
@bp.route('/', methods=['GET'])
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@bp.route('/pair', methods=['GET'])
|
||||
def pair():
|
||||
return render_template('pair.html')
|
||||
|
||||
|
||||
@bp.route('/success', methods=['GET'])
|
||||
def success():
|
||||
return render_template('success.html')
|
||||
|
||||
|
||||
@bp.route('/failure', methods=['GET'])
|
||||
def failure():
|
||||
return render_template('failure.html')
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "1.0",
|
||||
"title": "VEL Connect API",
|
||||
"description": "This API provides an interface with the database for pairing and controlling headsets while within a VEL app."
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "http://velconnect.ugavel.com/api/"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:5000/api/"
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
{
|
||||
"name": "Read"
|
||||
},
|
||||
{
|
||||
"name": "Write"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/get_all_headsets": {
|
||||
"get": {
|
||||
"summary": "Get all headset data",
|
||||
"description" : "Gets a list of all headset data that has been stored",
|
||||
"tags": ["Read"],
|
||||
"parameters": [
|
||||
]
|
||||
}
|
||||
},
|
||||
"/pair_headset/{pairing_code}": {
|
||||
"get": {
|
||||
"summary": "Pair a headset by code",
|
||||
"description" : "Tries to pair to a headset with a supplied pairing code. Returns the hw_id if successfully paired. Used by the website for pairing.",
|
||||
"tags": ["Write"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "pairing_code",
|
||||
"example": "1234",
|
||||
"in": "path",
|
||||
"description": "Pairing Code",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/update_pairing_code": {
|
||||
"post": {
|
||||
"summary": "Update Pairing Code",
|
||||
"description": "This is called by the VR application on login or when the pairing code is reset. If this is the first time this headset is seen, it also creates this headset in the database. A JSON body must be submitted with hw_id and pairing_code defined.",
|
||||
"tags": ["Write"],
|
||||
"parameters": [
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"/get_headset_details/{hw_id}": {
|
||||
"get": {
|
||||
"summary": "Get Headset Details",
|
||||
"description" : "Gets the info associated with a specific hardware id.",
|
||||
"tags": ["Read"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hw_id",
|
||||
"example": "123456789",
|
||||
"in": "path",
|
||||
"description": "Hardware id of the device",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/get_room_details/{room_id}": {
|
||||
"get": {
|
||||
"summary": "Get Room Details",
|
||||
"description" : "Gets the info associated with a specific room id. Used by the website to show initial data, and polled often by the headset.",
|
||||
"tags": ["Read"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "room_id",
|
||||
"example": "1234",
|
||||
"in": "path",
|
||||
"description": "Room id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/update_room/{room_id}": {
|
||||
"post": {
|
||||
"summary": "Update Room Details",
|
||||
"description" : "Sets the info associated with a specific room id. The room id is specified in the url and the data is specified in the JSON body. Used by both the website and headset.",
|
||||
"tags": ["Write"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "room_id",
|
||||
"example": "1234",
|
||||
"in": "path",
|
||||
"description": "Room id",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"/update_user_count": {
|
||||
"post": {
|
||||
"summary": "Update User Count",
|
||||
"description" : "Updates the current user count for a room and globally.",
|
||||
"tags": ["User Count"],
|
||||
"parameters": [
|
||||
]
|
||||
}
|
||||
},
|
||||
"/get_user_count": {
|
||||
"get": {
|
||||
"summary": "Get User Count",
|
||||
"description" : "Gets a list of the recent user counts.",
|
||||
"tags": ["User Count"],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "hours",
|
||||
"example": "24",
|
||||
"in": "path",
|
||||
"description": "Number of hours to get user counts for",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
},
|
||||
"securitySchemes": {
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "x-api-key",
|
||||
"in": "query"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"BasicAuth": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
|
@ -1,9 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/mstile-150x150.png"/>
|
||||
<TileColor>#b91d47</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
||||
|
Before Width: | Height: | Size: 815 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M605 1500 l600 -600 97 0 98 0 0 600 0 600 -100 0 -100 0 0 -452 0
|
||||
-453 -453 453 -452 452 -145 0 -145 0 600 -600z"/>
|
||||
<path d="M1500 2000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||
<path d="M2200 1500 l0 -600 398 2 397 3 3 98 3 97 -301 0 -300 0 -2 498 -3
|
||||
497 -97 3 -98 3 0 -601z"/>
|
||||
<path d="M1500 1500 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||
<path d="M1500 1000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 977 B |
|
|
@ -1,19 +0,0 @@
|
|||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/android-chrome-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#ffffff",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
|
Before Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 437 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
|
@ -1,155 +0,0 @@
|
|||
|
||||
function httpGetAsync(theUrl, callback, failCallback) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState == 4) {
|
||||
if (xmlHttp.status == 200) {
|
||||
callback(xmlHttp.responseText);
|
||||
} else {
|
||||
failCallback(xmlHttp.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlHttp.open("GET", theUrl, true); // true for asynchronous
|
||||
xmlHttp.send(null);
|
||||
}
|
||||
|
||||
|
||||
function httpPostAsync(theUrl, data, callback, failCallback) {
|
||||
var xmlHttp = new XMLHttpRequest();
|
||||
xmlHttp.onreadystatechange = function () {
|
||||
if (xmlHttp.readyState == 4) {
|
||||
if (xmlHttp.status == 200) {
|
||||
callback(xmlHttp.responseText);
|
||||
} else {
|
||||
failCallback(xmlHttp.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
xmlHttp.open("POST", theUrl, true); // true for asynchronous
|
||||
xmlHttp.setRequestHeader('Content-type', 'application/json');
|
||||
xmlHttp.send(JSON.stringify(data));
|
||||
}
|
||||
|
||||
function setCookie(cname, cvalue, exdays) {
|
||||
const d = new Date();
|
||||
d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000));
|
||||
let expires = "expires=" + d.toUTCString();
|
||||
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
|
||||
}
|
||||
|
||||
|
||||
function getCookie(cname) {
|
||||
let name = cname + "=";
|
||||
let decodedCookie = decodeURIComponent(document.cookie);
|
||||
let ca = decodedCookie.split(';');
|
||||
for (let i = 0; i < ca.length; i++) {
|
||||
let c = ca[i];
|
||||
while (c.charAt(0) == ' ') {
|
||||
c = c.substring(1);
|
||||
}
|
||||
if (c.indexOf(name) == 0) {
|
||||
return c.substring(name.length, c.length);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function writeClass(className, data) {
|
||||
if (data == undefined || data == null || data.toString() == 'undefined') {
|
||||
data = "";
|
||||
}
|
||||
|
||||
let elements = document.getElementsByClassName(className);
|
||||
Array.from(elements).forEach(e => {
|
||||
e.innerHTML = data;
|
||||
});
|
||||
}
|
||||
|
||||
function writeId(idName, data) {
|
||||
if (data == undefined || data == null || data.toString() == 'undefined') {
|
||||
data = "";
|
||||
}
|
||||
|
||||
document.getElementById(idName).innerHTML = data;
|
||||
}
|
||||
|
||||
function writeValue(className, data) {
|
||||
if (data == undefined || data == null || data.toString() == 'undefined') {
|
||||
data = "";
|
||||
}
|
||||
|
||||
let elements = document.getElementsByClassName(className);
|
||||
Array.from(elements).forEach(e => {
|
||||
e.value = data;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function writeSrc(className, data) {
|
||||
if (data == undefined || data == null || data.toString() == 'undefined') {
|
||||
data = "";
|
||||
}
|
||||
|
||||
let elements = document.getElementsByClassName(className);
|
||||
Array.from(elements).forEach(e => {
|
||||
e.src = data;
|
||||
});
|
||||
}
|
||||
|
||||
function timeSince(date) {
|
||||
|
||||
let seconds = Math.floor((new Date() - date) / 1000);
|
||||
|
||||
let interval = seconds / 31536000;
|
||||
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " years";
|
||||
}
|
||||
interval = seconds / 2592000;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " months";
|
||||
}
|
||||
interval = seconds / 86400;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " days";
|
||||
}
|
||||
interval = seconds / 3600;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " hours";
|
||||
}
|
||||
interval = seconds / 60;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " minutes";
|
||||
}
|
||||
return Math.floor(seconds) + " seconds";
|
||||
}
|
||||
|
||||
function timeSinceString(date) {
|
||||
|
||||
date = Date.parse(date);
|
||||
let seconds = Math.floor((new Date() - date) / 1000);
|
||||
|
||||
let interval = seconds / 31536000;
|
||||
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " years";
|
||||
}
|
||||
interval = seconds / 2592000;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " months";
|
||||
}
|
||||
interval = seconds / 86400;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " days";
|
||||
}
|
||||
interval = seconds / 3600;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " hours";
|
||||
}
|
||||
interval = seconds / 60;
|
||||
if (interval > 1) {
|
||||
return Math.floor(interval) + " minutes";
|
||||
}
|
||||
return Math.floor(seconds) + " seconds";
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
|
||||
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
||||
<title>API Reference</title>
|
||||
</head>
|
||||
<body>
|
||||
<rapi-doc
|
||||
render-style = "read"
|
||||
primary-color = "#bc1f2d"
|
||||
show-header = "false"
|
||||
show-info = "true"
|
||||
spec-url = "{% include 'api_url.html' %}/api/api_spec.json"
|
||||
default-schema-tab = 'example'
|
||||
|
||||
>
|
||||
|
||||
|
||||
<div slot="nav-logo" style="display: flex; align-items: center; justify-content: center;">
|
||||
<img src = "/static/favicons/android-chrome-256x256.png" style="width:10em; margin: auto;" />
|
||||
</div>
|
||||
</rapi-doc>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'single.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #333;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: 8em auto;
|
||||
width: max-content;
|
||||
font-family: arial, sans-serif;
|
||||
color: #ddd;
|
||||
}
|
||||
</style>
|
||||
<div class="centered">
|
||||
🤮 FAIL 🤡
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
@ -1,264 +0,0 @@
|
|||
{% extends 'single.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
.container {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1em;
|
||||
box-shadow: 0 0 2em #0003;
|
||||
}
|
||||
|
||||
input.btn {
|
||||
cursor: auto;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="loading"><br><br>
|
||||
<div class="loading loading-lg"></div>
|
||||
</div>
|
||||
<div id="failure" style="display: none;"><br><br><br>☹️</div>
|
||||
|
||||
|
||||
<div id="headset_details" style="display: none;">
|
||||
<div class="panel card">
|
||||
<div class="card-image">
|
||||
<img class="img-responsive" src="/static/img/mini_landscape.png" alt="conVRged Logo">
|
||||
</div>
|
||||
<div class="panel-header text-center">
|
||||
<!-- <img src="/static/favicons/android-chrome-192x192.png" alt="Avatar" style="height:5em;" /> -->
|
||||
<div class="panel-title h5 mt-10">Headset Info</div>
|
||||
<code class="panel-subtitle hw_id">---</code>
|
||||
<br>
|
||||
<br>
|
||||
<a href="/pair"><button class="btn btn-primary btn-lg tooltip tooltip-bottom" data-tooltip="Enter a new pairing code">Pair New</button></a>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">Current Room</div>
|
||||
<input class="btn current_room" type="text" id="current_room" placeholder="----">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_room_id"
|
||||
data-tooltip="Set Room ID">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">First Seen</div>
|
||||
<div class="tile-subtitle date_created">---</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">Last Login</div>
|
||||
<div class="tile-subtitle last_used">---</div>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="divider text-center" data-content="User Settings"></div>
|
||||
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">User Name</div>
|
||||
<input class="btn user_name" type="text" id="user_name" placeholder="----">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_user_name"
|
||||
data-tooltip="Update in VR">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">Avatar URL</div>
|
||||
<div class="tile-subtitle"><a href="https://convrged.readyplayer.me" target="blank">Create New Avatar</a></div>
|
||||
<input class="btn avatar_url" type="text" id="avatar_url" placeholder="----">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_avatar_url"
|
||||
data-tooltip="Update in VR">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">User Color</div>
|
||||
<input class="btn user_color coloris" type="text" id="user_color" placeholder="#ffffff">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_user_color"
|
||||
data-tooltip="Set User Color">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
||||
<div class="divider text-center" data-content="Room Settings"></div>
|
||||
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">TV URL</div>
|
||||
<input class="btn tv_url" type="text" id="tv_url" placeholder="----">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_tv_url"
|
||||
data-tooltip="Update in VR">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="tile tile-centered">
|
||||
<div class="tile-content">
|
||||
<div class="tile-title text-bold">Carpet Color</div>
|
||||
<input class="btn carpet_color coloris" type="text" id="carpet_color" placeholder="#ffffff">
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button class="btn btn-primary btn-lg tooltip tooltip-left" id="set_carpet_color"
|
||||
data-tooltip="Set Carpet Color">Set</button>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="/static/js/coloris.min.js"></script>
|
||||
<script>
|
||||
|
||||
let submit_button = document.getElementById('submit_pairing_code');
|
||||
let pair_code_input = document.getElementById('pair_code');
|
||||
let loading = document.getElementById('loading');
|
||||
let enter_pairing_id = document.getElementById('enter_pairing_id');
|
||||
let headset_details = document.getElementById('headset_details');
|
||||
let hw_id_field = document.getElementById('hw_id');
|
||||
let failure = document.getElementById('failure');
|
||||
let current_room = document.getElementById('current_room');
|
||||
let set_room_id = document.getElementById('set_room_id');
|
||||
let user_color = document.getElementById('user_color');
|
||||
let carpet_color = document.getElementById('carpet_color');
|
||||
|
||||
|
||||
// check cookie
|
||||
let hw_id = getCookie('hw_id');
|
||||
if (hw_id != "") {
|
||||
|
||||
httpGetAsync('{% include "api_url.html" %}/api/get_state/' + hw_id, (resp) => {
|
||||
console.log(resp);
|
||||
let respData = JSON.parse(resp);
|
||||
|
||||
writeClass('hw_id', respData['user']['hw_id']);
|
||||
writeValue('current_room', respData['user']['current_room']);
|
||||
writeClass('date_created', respData['user']['date_created'] + "<br>" + timeSinceString(respData['user']['date_created']) + " ago");
|
||||
writeClass('last_used', respData['user']['last_used'] + "<br>" + timeSinceString(respData['user']['last_used']) + " ago");
|
||||
writeValue('user_color', respData['user']['user_color']);
|
||||
user_color.parentElement.style.color = respData['user']['user_color'];
|
||||
writeValue('user_name', respData['user']['user_name']);
|
||||
writeValue('avatar_url', respData['user']['avatar_url']);
|
||||
if (respData['room']) {
|
||||
writeValue('tv_url', respData['room']['tv_url']);
|
||||
writeValue('carpet_color', respData['room']['carpet_color']);
|
||||
carpet_color.parentElement.style.color = respData['room']['carpet_color'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
Coloris({
|
||||
el: '.coloris',
|
||||
swatches: [
|
||||
'#264653',
|
||||
'#2a9d8f',
|
||||
'#e9c46a',
|
||||
'#f4a261',
|
||||
'#e76f51',
|
||||
'#d62828',
|
||||
'#023e8a',
|
||||
'#0077b6',
|
||||
'#0096c7',
|
||||
'#00b4d8',
|
||||
'#48cae4',
|
||||
]
|
||||
});
|
||||
|
||||
loading.style.display = "none";
|
||||
headset_details.style.display = "block";
|
||||
}, (status) => {
|
||||
loading.style.display = "none";
|
||||
failure.style.display = "block";
|
||||
window.location.href = "/pair";
|
||||
});
|
||||
|
||||
function setUserData(data) {
|
||||
data["sender_id"] = Math.floor(Math.random()*10000000);
|
||||
httpPostAsync('{% include "api_url.html" %}/api/set_headset_details/' + hw_id,
|
||||
data,
|
||||
(resp) => { console.log('success'); },
|
||||
(status) => { console.log('fail'); }
|
||||
);
|
||||
}
|
||||
function setRoomData(data) {
|
||||
data["sender_id"] = Math.floor(Math.random()*10000000);
|
||||
httpPostAsync('{% include "api_url.html" %}/api/set_room_details/' + current_room.value,
|
||||
data,
|
||||
(resp) => { console.log('success'); },
|
||||
(status) => { console.log('fail'); }
|
||||
);
|
||||
}
|
||||
|
||||
set_room_id.addEventListener('click', () => {
|
||||
setUserData({ "current_room": current_room.value });
|
||||
});
|
||||
document.getElementById('set_user_color').addEventListener('click', () => {
|
||||
setUserData({ "user_color": document.getElementById('user_color').value });
|
||||
});
|
||||
document.getElementById('set_user_name').addEventListener('click', () => {
|
||||
setUserData({ "user_name": document.getElementById('user_name').value });
|
||||
});
|
||||
document.getElementById('set_tv_url').addEventListener('click', () => {
|
||||
setRoomData({ "tv_url": document.getElementById('tv_url').value });
|
||||
});
|
||||
document.getElementById('set_carpet_color').addEventListener('click', () => {
|
||||
setRoomData({ "carpet_color": document.getElementById('carpet_color').value });
|
||||
});
|
||||
document.getElementById('set_avatar_url').addEventListener('click', () => {
|
||||
setUserData({ "avatar_url": document.getElementById('avatar_url').value });
|
||||
});
|
||||
|
||||
} else {
|
||||
window.location.href = "/pair";
|
||||
}
|
||||
|
||||
|
||||
|
||||
Coloris({
|
||||
el: '.coloris',
|
||||
swatches: [
|
||||
'#264653',
|
||||
'#2a9d8f',
|
||||
'#e9c46a',
|
||||
'#f4a261',
|
||||
'#e76f51',
|
||||
'#d62828',
|
||||
'#023e8a',
|
||||
'#0077b6',
|
||||
'#0096c7',
|
||||
'#00b4d8',
|
||||
'#48cae4',
|
||||
]
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,69 +0,0 @@
|
|||
{% extends 'single.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #bc1f2d;
|
||||
}
|
||||
|
||||
#pair_code {
|
||||
max-width: 4em;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1em;
|
||||
box-shadow: 0 0 2em #0003;
|
||||
}
|
||||
|
||||
input.btn {
|
||||
cursor: auto;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<div class="card">
|
||||
<div class="card-image">
|
||||
<img src="/static/img/pair_code_screenshot.png" class="img-responsive">
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<div class="card-title h5">Enter Pairing Code</div>
|
||||
<div class="card-subtitle text-gray"></div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
You can find the code in the bottom left of your menu tablet in conVRged.
|
||||
</div>
|
||||
<div class="card-footer centered">
|
||||
<input class="btn" type="text" id="pair_code" placeholder="0000">
|
||||
<button class="btn btn-primary" id="submit_pairing_code">Submit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
let submit_button = document.getElementById('submit_pairing_code');
|
||||
let pair_code_input = document.getElementById('pair_code');
|
||||
submit_button.addEventListener('click', () => {
|
||||
httpGetAsync("{% include 'api_url.html' %}/api/pair_headset/" + pair_code_input.value, (resp) => {
|
||||
console.log(resp);
|
||||
let respData = JSON.parse(resp);
|
||||
if (respData['hw_id'] != '') {
|
||||
setCookie('hw_id', respData['hw_id'], 60);
|
||||
window.location.href = "/";
|
||||
}
|
||||
}, (status) => {
|
||||
window.location.href = "/failure";
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
|
||||
<link rel="manifest" href="/static/favicons/site.webmanifest">
|
||||
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#b91d47">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<title>VEL Connect</title>
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/spectre.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/spectre-exp.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/spectre-icons.min.css">
|
||||
<link rel="stylesheet" type="text/css" href="/static/css/coloris.min.css">
|
||||
<script src="/static/js/util.js"></script>
|
||||
<style>
|
||||
.container {
|
||||
max-width: 30em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1em;
|
||||
box-shadow: 0 0 2em #0003;
|
||||
}
|
||||
|
||||
input.btn {
|
||||
cursor: auto;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
{% extends 'single.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
background-color: #333;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: 8em auto;
|
||||
width: max-content;
|
||||
font-family: arial, sans-serif;
|
||||
color: #ddd;
|
||||
}
|
||||
</style>
|
||||
<div class="centered">
|
||||
🎉 SUCCESS 🎉
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||