tvid is a redirection manager for televisions, lobby displays, status dashboards, and the like, so that you can set all of the displays in your organization to one URL and manage them via the web.
Nevar pievienot vairāk kā 25 tēmas
Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
189 rindas
5.9 KiB
189 rindas
5.9 KiB
#!/usr/bin/env python3
|
|
|
|
# this is a quick hack, to be improved later. trying to do the simplest
|
|
# thing that will possibly work.
|
|
|
|
## # TODO
|
|
##
|
|
## * put page content into templates/static files
|
|
## * clean up FIXME
|
|
|
|
import bottle
|
|
from bottle import route, run, request, response, redirect
|
|
from bottle import HTTPError, template
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from bottle.ext import sqlalchemy
|
|
from pprint import pprint
|
|
from sqlalchemy import create_engine
|
|
import urllib.parse
|
|
import os
|
|
import random
|
|
import string
|
|
from datetime import datetime, timedelta
|
|
|
|
VERSION = "1.0.0"
|
|
PORT = os.environ.get("PORT", 8080)
|
|
DEBUG = os.environ.get("DEBUG", False)
|
|
SQLITE_FILENAME = os.environ.get("SQLITE_FILENAME", "/data/db.sqlite")
|
|
DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///" + SQLITE_FILENAME)
|
|
ADMIN_PSK = os.environ.get("ADMIN_PSK", "hunter2")
|
|
|
|
# sorry for global
|
|
SQLBASE = declarative_base()
|
|
|
|
# FIXME make this skip letters not in base58
|
|
def randomUpper(count):
|
|
return "".join(random.choice(string.ascii_uppercase) for _ in range(count))
|
|
|
|
|
|
# FIXME maybe make the IDs longer
|
|
def genRandomTVID():
|
|
tvid = str(randomUpper(3) + "-" + randomUpper(3))
|
|
return tvid
|
|
|
|
|
|
def serve():
|
|
app = bottle.Bottle()
|
|
|
|
# pull in models
|
|
from .db import TV
|
|
|
|
engine = create_engine(DATABASE_URL, echo=False)
|
|
|
|
plugin = sqlalchemy.Plugin(
|
|
engine,
|
|
SQLBASE.metadata,
|
|
keyword="db",
|
|
create=True,
|
|
commit=True,
|
|
use_kwargs=False,
|
|
)
|
|
|
|
app.install(plugin)
|
|
|
|
# here we cookie them if they have no cookie, then redirect them to the
|
|
# cookie'd value (whether preexisting or new).
|
|
@app.get("/")
|
|
def indexpage():
|
|
c = request.get_cookie("displayid")
|
|
if c:
|
|
# redirect
|
|
return redirect("/tv/" + c)
|
|
else:
|
|
newid = genRandomTVID()
|
|
response.set_cookie("displayid", newid)
|
|
return redirect("/tv/" + newid)
|
|
|
|
@app.get("/style.css")
|
|
def stylesheet():
|
|
response.content_type = "text/css"
|
|
return template("style")
|
|
|
|
# here we check to see if they have a redirect URL in the db. if they do
|
|
# we send them there. if they don't, we display their ID really big,
|
|
# reloading the page once per hour.
|
|
@app.get("/tv/<displayid>")
|
|
def tvpage(db, displayid=None):
|
|
# FIXME check for cookie, this is broken
|
|
if displayid is None:
|
|
return template("nocookie")
|
|
|
|
# check db for tv id
|
|
tv = db.query(TV).filter_by(displayid=displayid).first()
|
|
if tv:
|
|
tv.lastSeen = datetime.now()
|
|
db.add(tv)
|
|
if tv.target:
|
|
return redirect(tv.target)
|
|
else:
|
|
return template("displayid", id=displayid, version=VERSION)
|
|
else:
|
|
# otherwise, just show their display ID bigly and keep them
|
|
# bouncing periodically until some admin configures them
|
|
# a target:
|
|
# update lastseen here:
|
|
newtv = TV(displayid=displayid, lastSeen=datetime.now())
|
|
db.add(newtv)
|
|
return template("displayid", id=displayid, version=VERSION)
|
|
|
|
@app.get("/admin/edit/<displayid>")
|
|
def displayeditform(db, displayid=None):
|
|
c = request.get_cookie("psk")
|
|
if not c:
|
|
return redirect("/login")
|
|
if c != ADMIN_PSK:
|
|
return redirect("/logout")
|
|
if not displayid:
|
|
return redirect("/admin")
|
|
tv = db.query(TV).filter_by(displayid=displayid).first()
|
|
if tv is None:
|
|
return redirect("/admin")
|
|
return template("displayeditform", tv=tv, version=VERSION)
|
|
|
|
@app.post("/admin/edit")
|
|
def displayedithandler(db):
|
|
# FIXME SECURITY csrf issue
|
|
c = request.get_cookie("psk")
|
|
if not c:
|
|
return redirect("/login")
|
|
if c != ADMIN_PSK:
|
|
return redirect("/logout")
|
|
displayid = request.forms.get("displayid")
|
|
tv = db.query(TV).filter_by(displayid=displayid).first()
|
|
if tv is None:
|
|
return redirect("/admin")
|
|
# FIXME make sure this is a valid URL
|
|
tv.target = request.forms.get("target")
|
|
tv.memo = request.forms.get("formmemo")
|
|
db.add(tv)
|
|
db.commit()
|
|
return redirect("/admin")
|
|
|
|
# here we display the administration list of TVs if logged in
|
|
# if logged out then redirect to /login
|
|
# FIXME make this use sessions instead of just storing PSK in a cookie
|
|
# https://bottlepy.org/docs/dev/recipes.html
|
|
@app.get("/admin")
|
|
def adminpage(db):
|
|
c = request.get_cookie("psk")
|
|
if not c:
|
|
return redirect("/login")
|
|
if c != ADMIN_PSK:
|
|
return redirect("/logout")
|
|
|
|
# first, cleanup db of old entries:
|
|
week_ago = datetime.now() - timedelta(days=7)
|
|
db.query(TV).filter(TV.lastSeen < week_ago).delete()
|
|
db.commit()
|
|
|
|
tvs = db.query(TV).order_by(TV.lastSeen.desc())
|
|
response.headers["Cache-Control"] = "no-cache"
|
|
return template("adminpanel", tvs=tvs, version=VERSION)
|
|
|
|
# here we ask for a password:
|
|
@app.get("/login")
|
|
def loginform():
|
|
msg = request.GET.msg
|
|
return template("loginform", version=VERSION, msg=msg)
|
|
|
|
@app.post("/checklogin")
|
|
def checklogin():
|
|
attemptedPass = request.forms.get("password")
|
|
if not attemptedPass:
|
|
return redirect(
|
|
"/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
|
|
)
|
|
if attemptedPass != ADMIN_PSK:
|
|
return redirect(
|
|
"/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
|
|
)
|
|
# password is right, cookie them:
|
|
response.set_cookie("psk", attemptedPass)
|
|
return redirect("/admin")
|
|
|
|
@app.get("/logout")
|
|
def logout():
|
|
response.set_cookie("psk", "")
|
|
return redirect("/login")
|
|
|
|
app.run(host="0.0.0.0", port=PORT, debug=DEBUG)
|
|
|