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.
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

190 lines
5.9KB

  1. #!/usr/bin/env python3
  2. # this is a quick hack, to be improved later. trying to do the simplest
  3. # thing that will possibly work.
  4. ## # TODO
  5. ##
  6. ## * put page content into templates/static files
  7. ## * clean up FIXME
  8. import bottle
  9. from bottle import route, run, request, response, redirect
  10. from bottle import HTTPError, template
  11. from sqlalchemy.ext.declarative import declarative_base
  12. from bottle.ext import sqlalchemy
  13. from pprint import pprint
  14. from sqlalchemy import create_engine
  15. import urllib.parse
  16. import os
  17. import random
  18. import string
  19. from datetime import datetime, timedelta
  20. VERSION = "1.0.0"
  21. PORT = os.environ.get("PORT", 8080)
  22. DEBUG = os.environ.get("DEBUG", False)
  23. SQLITE_FILENAME = os.environ.get("SQLITE_FILENAME", "/data/db.sqlite")
  24. DATABASE_URL = os.environ.get("DATABASE_URL", "sqlite:///" + SQLITE_FILENAME)
  25. ADMIN_PSK = os.environ.get("ADMIN_PSK", "hunter2")
  26. # sorry for global
  27. SQLBASE = declarative_base()
  28. # FIXME make this skip letters not in base58
  29. def randomUpper(count):
  30. return "".join(random.choice(string.ascii_uppercase) for _ in range(count))
  31. # FIXME maybe make the IDs longer
  32. def genRandomTVID():
  33. tvid = str(randomUpper(3) + "-" + randomUpper(3))
  34. return tvid
  35. def serve():
  36. app = bottle.Bottle()
  37. # pull in models
  38. from .db import TV
  39. engine = create_engine(DATABASE_URL, echo=False)
  40. plugin = sqlalchemy.Plugin(
  41. engine,
  42. SQLBASE.metadata,
  43. keyword="db",
  44. create=True,
  45. commit=True,
  46. use_kwargs=False,
  47. )
  48. app.install(plugin)
  49. # here we cookie them if they have no cookie, then redirect them to the
  50. # cookie'd value (whether preexisting or new).
  51. @app.get("/")
  52. def indexpage():
  53. c = request.get_cookie("displayid")
  54. if c:
  55. # redirect
  56. return redirect("/tv/" + c)
  57. else:
  58. newid = genRandomTVID()
  59. response.set_cookie("displayid", newid)
  60. return redirect("/tv/" + newid)
  61. @app.get("/style.css")
  62. def stylesheet():
  63. response.content_type = "text/css"
  64. return template("style")
  65. # here we check to see if they have a redirect URL in the db. if they do
  66. # we send them there. if they don't, we display their ID really big,
  67. # reloading the page once per hour.
  68. @app.get("/tv/<displayid>")
  69. def tvpage(db, displayid=None):
  70. # FIXME check for cookie, this is broken
  71. if displayid is None:
  72. return template("nocookie")
  73. # check db for tv id
  74. tv = db.query(TV).filter_by(displayid=displayid).first()
  75. if tv:
  76. tv.lastSeen = datetime.now()
  77. db.add(tv)
  78. if tv.target:
  79. return redirect(tv.target)
  80. else:
  81. return template("displayid", id=displayid, version=VERSION)
  82. else:
  83. # otherwise, just show their display ID bigly and keep them
  84. # bouncing periodically until some admin configures them
  85. # a target:
  86. # update lastseen here:
  87. newtv = TV(displayid=displayid, lastSeen=datetime.now())
  88. db.add(newtv)
  89. return template("displayid", id=displayid, version=VERSION)
  90. @app.get("/admin/edit/<displayid>")
  91. def displayeditform(db, displayid=None):
  92. c = request.get_cookie("psk")
  93. if not c:
  94. return redirect("/login")
  95. if c != ADMIN_PSK:
  96. return redirect("/logout")
  97. if not displayid:
  98. return redirect("/admin")
  99. tv = db.query(TV).filter_by(displayid=displayid).first()
  100. if tv is None:
  101. return redirect("/admin")
  102. return template("displayeditform", tv=tv, version=VERSION)
  103. @app.post("/admin/edit")
  104. def displayedithandler(db):
  105. # FIXME SECURITY csrf issue
  106. c = request.get_cookie("psk")
  107. if not c:
  108. return redirect("/login")
  109. if c != ADMIN_PSK:
  110. return redirect("/logout")
  111. displayid = request.forms.get("displayid")
  112. tv = db.query(TV).filter_by(displayid=displayid).first()
  113. if tv is None:
  114. return redirect("/admin")
  115. # FIXME make sure this is a valid URL
  116. tv.target = request.forms.get("target")
  117. tv.memo = request.forms.get("formmemo")
  118. db.add(tv)
  119. db.commit()
  120. return redirect("/admin")
  121. # here we display the administration list of TVs if logged in
  122. # if logged out then redirect to /login
  123. # FIXME make this use sessions instead of just storing PSK in a cookie
  124. # https://bottlepy.org/docs/dev/recipes.html
  125. @app.get("/admin")
  126. def adminpage(db):
  127. c = request.get_cookie("psk")
  128. if not c:
  129. return redirect("/login")
  130. if c != ADMIN_PSK:
  131. return redirect("/logout")
  132. # first, cleanup db of old entries:
  133. week_ago = datetime.now() - timedelta(days=7)
  134. db.query(TV).filter(TV.lastSeen < week_ago).delete()
  135. db.commit()
  136. tvs = db.query(TV).order_by(TV.lastSeen.desc())
  137. response.headers["Cache-Control"] = "no-cache"
  138. return template("adminpanel", tvs=tvs, version=VERSION)
  139. # here we ask for a password:
  140. @app.get("/login")
  141. def loginform():
  142. msg = request.GET.msg
  143. return template("loginform", version=VERSION, msg=msg)
  144. @app.post("/checklogin")
  145. def checklogin():
  146. attemptedPass = request.forms.get("password")
  147. if not attemptedPass:
  148. return redirect(
  149. "/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
  150. )
  151. if attemptedPass != ADMIN_PSK:
  152. return redirect(
  153. "/login?msg=" + urllib.parse.quote_plus(u"Incorrect password.")
  154. )
  155. # password is right, cookie them:
  156. response.set_cookie("psk", attemptedPass)
  157. return redirect("/admin")
  158. @app.get("/logout")
  159. def logout():
  160. response.set_cookie("psk", "")
  161. return redirect("/login")
  162. app.run(host="0.0.0.0", port=PORT, debug=DEBUG)