commit
3ae7b05b9b
@ -0,0 +1,3 @@ |
||||
dist/ |
||||
db.sqlite |
||||
build/ |
@ -0,0 +1,3 @@ |
||||
dist/ |
||||
db.sqlite |
||||
build/ |
@ -0,0 +1,75 @@ |
||||
# focal amd64 as of 2020-03-10 |
||||
FROM ubuntu@sha256:d050ed7278c16ff627e4a70d7d353f1a2ec74d8a0b66e5a865356d92f5f6d87b |
||||
|
||||
#######################################################################33 |
||||
## Mirror Setup |
||||
## - option to use local mirror to speed build |
||||
#######################################################################33 |
||||
ARG UBUNTU_MIRROR=http://archive.ubuntu.com/ubuntu |
||||
RUN echo "deb $UBUNTU_MIRROR focal main universe restricted multiverse" > /etc/apt/sources.list.new && \ |
||||
echo "deb $UBUNTU_MIRROR focal-updates main universe restricted multiverse" >> /etc/apt/sources.list.new && \ |
||||
echo "deb $UBUNTU_MIRROR focal-security main universe restricted multiverse" >> /etc/apt/sources.list.new && \ |
||||
echo "deb $UBUNTU_MIRROR focal-backports main universe restricted multiverse" >> /etc/apt/sources.list.new && \ |
||||
mv /etc/apt/sources.list.new /etc/apt/sources.list |
||||
|
||||
#######################################################################33 |
||||
## Versions |
||||
#######################################################################33 |
||||
# master as of 2020-03-10 |
||||
ARG PYENV_COMMIT=df9fa1dc30b6448ef8605e2c2d4dfc2a94d6a35d |
||||
ARG PYTHON_VERSION=3.8.1 |
||||
|
||||
#######################################################################33 |
||||
## Packages |
||||
#######################################################################33 |
||||
ARG DEBIAN_FRONTEND=noninteractive |
||||
RUN apt update && \ |
||||
apt upgrade -y && \ |
||||
apt install -y \ |
||||
build-essential \ |
||||
curl \ |
||||
git \ |
||||
libbz2-dev \ |
||||
libffi-dev \ |
||||
liblzma-dev \ |
||||
libncurses5-dev \ |
||||
libncursesw5-dev \ |
||||
libreadline-dev \ |
||||
libsqlite3-dev \ |
||||
libssl-dev \ |
||||
llvm \ |
||||
locales \ |
||||
make \ |
||||
python-openssl \ |
||||
tk-dev \ |
||||
wget \ |
||||
xz-utils \ |
||||
zlib1g-dev \ |
||||
&& \ |
||||
mkdir -p /var/app && \ |
||||
git clone https://github.com/pyenv/pyenv.git /usr/local/pyenv && \ |
||||
cd /usr/local/pyenv && \ |
||||
git checkout $PYENV_COMMIT |
||||
|
||||
ENV PYENV_ROOT /usr/local/pyenv |
||||
ENV PATH $PYENV_ROOT/bin:$PATH |
||||
|
||||
|
||||
#######################################################################33 |
||||
## Python |
||||
#######################################################################33 |
||||
RUN pyenv install $PYTHON_VERSION && \ |
||||
pyenv global $PYTHON_VERISON && \ |
||||
pip3 install pipenv |
||||
|
||||
#######################################################################33 |
||||
## Install Deps |
||||
#######################################################################33 |
||||
ADD ./Pipfile ./Pipfile.lock /var/app/ |
||||
WORKDIR /var/app |
||||
RUN pipenv install --python $(which python3) |
||||
|
||||
#######################################################################33 |
||||
## Install App |
||||
#######################################################################33 |
||||
ADD ./* /var/app/ |
@ -0,0 +1,15 @@ |
||||
export PYTHONPATH =: $(PWD)
|
||||
|
||||
default: docker |
||||
|
||||
peinstall: |
||||
pipenv install --python $(shell which python3)
|
||||
|
||||
serve: |
||||
pipenv run python ./bin/tvidd
|
||||
|
||||
clean: |
||||
rm -rf build dist tvid.egg-info
|
||||
|
||||
docker: |
||||
docker build --build-arg UBUNTU_MIRROR="http://ubuntu.datavi.be/ubuntu" .
|
@ -0,0 +1,15 @@ |
||||
[[source]] |
||||
name = "pypi" |
||||
url = "https://pypi.org/simple" |
||||
verify_ssl = true |
||||
|
||||
[dev-packages] |
||||
|
||||
[packages] |
||||
bottle = "*" |
||||
pprint = "*" |
||||
sqlalchemy = "*" |
||||
bottle-sqlalchemy = "*" |
||||
|
||||
[requires] |
||||
python_version = "3.8" |
@ -0,0 +1,50 @@ |
||||
{ |
||||
"_meta": { |
||||
"hash": { |
||||
"sha256": "7216cb0c490cfe6852be9bc21eeac5f3f08cf65b39f7316fae3580a387938016" |
||||
}, |
||||
"pipfile-spec": 6, |
||||
"requires": { |
||||
"python_version": "3.8" |
||||
}, |
||||
"sources": [ |
||||
{ |
||||
"name": "pypi", |
||||
"url": "https://pypi.org/simple", |
||||
"verify_ssl": true |
||||
} |
||||
] |
||||
}, |
||||
"default": { |
||||
"bottle": { |
||||
"hashes": [ |
||||
"sha256:0819b74b145a7def225c0e83b16a4d5711fde751cd92bae467a69efce720f69e", |
||||
"sha256:43157254e88f32c6be16f8d9eb1f1d1472396a4e174ebd2bf62544854ecf37e7" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.12.18" |
||||
}, |
||||
"bottle-sqlalchemy": { |
||||
"hashes": [ |
||||
"sha256:ba6127f3aff2b78649781adbbee65518233dc481e9f9e32e3b050d1ad9551c17" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.4.3" |
||||
}, |
||||
"pprint": { |
||||
"hashes": [ |
||||
"sha256:c0fa22d1462351671ca098e9779bb26a23880011e93eea5f199a150ee7b92a16" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==0.1" |
||||
}, |
||||
"sqlalchemy": { |
||||
"hashes": [ |
||||
"sha256:b92d2de62e43499d85b1780274d1b562e5159c7996f6f04a9bb46cf681ced45f" |
||||
], |
||||
"index": "pypi", |
||||
"version": "==1.3.14" |
||||
} |
||||
}, |
||||
"develop": {} |
||||
} |
@ -0,0 +1,18 @@ |
||||
# tvid |
||||
|
||||
This is an app that lets you set all the kiosk/display TVs in your |
||||
organization to the same URL. Each will be cookied with a unique ID that |
||||
will display on each display in big letters, with no preconfiguration |
||||
required. |
||||
|
||||
Log in to the admin panel and enter the URL for that TV ID, and within 60 |
||||
seconds, that display will bounce to that URL, or any other time it turns |
||||
on. |
||||
|
||||
# todo |
||||
|
||||
* make display id animate: https://codepen.io/stezu/pen/cmLrI |
||||
|
||||
# author |
||||
|
||||
sneak <[sneak@sneak.berlin](mailto:sneak@sneak.berlin)> |
@ -0,0 +1,9 @@ |
||||
#!/usr/bin/env python3 |
||||
|
||||
from tvid import serve |
||||
|
||||
def main(): |
||||
serve() |
||||
|
||||
if __name__ == "__main__": |
||||
main() |
@ -0,0 +1,15 @@ |
||||
from setuptools import setup, find_packages |
||||
|
||||
setup( |
||||
name='tvid', |
||||
version='0.0.1', |
||||
packages=find_packages(), |
||||
license='WTFPL', |
||||
long_description=open('README.md').read(), |
||||
entry_points = { |
||||
'console_scripts': [ |
||||
'tvidd = tvid.tvid:serve' |
||||
], |
||||
}, |
||||
) |
||||
|
@ -0,0 +1,2 @@ |
||||
from .server import serve |
||||
|
@ -0,0 +1,16 @@ |
||||
from sqlalchemy import Column, Sequence, Integer, String, DateTime |
||||
from sqlalchemy.ext.declarative import declarative_base |
||||
|
||||
from .server import SQLBASE |
||||
|
||||
Base = SQLBASE |
||||
# YAGNI just set admin pw in an env var for now and dont support changing it |
||||
# yet |
||||
#class Settings(Base): |
||||
|
||||
class TV(Base): |
||||
__tablename__ = 'tvs' |
||||
id = Column(Integer, Sequence('id_seq'), primary_key=True) |
||||
displayid = Column(String(20)) |
||||
lastSeen = Column(DateTime) |
||||
target = Column(String(255)) |
@ -0,0 +1,130 @@ |
||||
#!/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 os |
||||
import random |
||||
import string |
||||
from datetime import datetime |
||||
|
||||
VERSION = '0.0.1' |
||||
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) |
||||
ADMINPSK = os.environ.get('ADMINPSK','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=True) |
||||
|
||||
plugin = sqlalchemy.Plugin( |
||||
engine, # SQLAlchemy engine created with create_engine function. |
||||
SQLBASE.metadata, # SQLAlchemy metadata, required only if create=True. |
||||
keyword='db', # Keyword used to inject session database in a route (default 'db'). |
||||
create=True, # If it is true, execute `metadata.create_all(engine)` when plugin is applied (default False). |
||||
commit=True, # If it is true, plugin commit changes after route is executed (default 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("tvid") |
||||
if c: |
||||
# redirect |
||||
redirect('/tv/' + c) |
||||
else: |
||||
newid = genRandomTVID() |
||||
response.set_cookie("tvid", newid) |
||||
redirect('/tv/' + newid) |
||||
|
||||
@app.get('/style.css') |
||||
def stylesheet(): |
||||
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 regex check id to make sure displayid is right format, |
||||
# return error if not |
||||
|
||||
if id 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: |
||||
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) |
||||
|
||||
# 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(): |
||||
c = request.get_cookie("adminpw") |
||||
|
||||
# FIXME check their 'adminpw' cookie here, redirect to /loign |
||||
return "Hello World!" |
||||
|
||||
# here we ask for a password and cookie them and bounce them back to /admin |
||||
@app.get('/login') |
||||
def checklogin(): |
||||
raise NotImplementedError() |
||||
#response.set_cookie("adminpw", whatever) |
||||
redirect('/login') |
||||
|
||||
@app.get('/logut') |
||||
def logout(): |
||||
response.set_cookie("adminpw", "") |
||||
redirect('/login') |
||||
|
||||
app.run(host='0.0.0.0', port=PORT, debug=DEBUG) |
@ -0,0 +1,20 @@ |
||||
<html> |
||||
<head> |
||||
<title>tv info page</title> |
||||
<meta http-equiv="refresh" content="60"> |
||||
<link rel="stylesheet" href="/style.css" /> |
||||
</head> |
||||
<body> |
||||
<div id="main"> |
||||
<p> |
||||
<i>Display ID:</i> |
||||
</p> |
||||
<h1>{{id}}</h1> |
||||
|
||||
<small>(They're only letters. I like India, O like |
||||
Oscar.)</small> |
||||
|
||||
<p><small>Powered by tvid v{{version}}</small></p> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,10 @@ |
||||
<html> |
||||
<head> |
||||
<title>tv info page</title> |
||||
<meta http-equiv="refresh" content="60; url=/"> |
||||
<link rel="stylesheet" href="/style.css" /> |
||||
</head> |
||||
<body> |
||||
<h1>Please enable cookies, they're required.</h1> |
||||
</body> |
||||
</html> |
@ -0,0 +1,10 @@ |
||||
body { |
||||
font-family: sans-serif; |
||||
width: 100%; |
||||
text-align: center; |
||||
font-size: 20pt |
||||
} |
||||
|
||||
h1 { |
||||
font-size: 48pt; |
||||
} |
Cargando…
Referencia en una nueva incidencia