From c4ef8d0e96fc963e15b220649ade59886d1d1d25 Mon Sep 17 00:00:00 2001
From: sneak
Date: Tue, 10 Mar 2020 18:52:30 -0700
Subject: [PATCH] 1.0.0 beta
---
.dockerignore | 1 +
Dockerfile | 91 ++++++++++++++++++++++-----------
Makefile | 4 +-
tvid/db.py | 1 +
tvid/server.py | 103 +++++++++++++++++++++++---------------
views/adminpagebase.tpl | 17 +++++++
views/adminpanel.tpl | 27 +++++++++-
views/base.tpl | 5 +-
views/displayeditform.tpl | 33 ++++++++++++
views/displayid.tpl | 35 +++++++++++--
views/loginform.tpl | 14 ++++--
views/navbar.tpl | 41 +++++++++++++++
views/style.tpl | 15 ++++--
13 files changed, 302 insertions(+), 85 deletions(-)
create mode 100644 views/adminpagebase.tpl
create mode 100644 views/displayeditform.tpl
create mode 100644 views/navbar.tpl
diff --git a/.dockerignore b/.dockerignore
index 947d3f5..e4b29ef 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,3 +1,4 @@
dist/
db.sqlite
build/
+.git
diff --git a/Dockerfile b/Dockerfile
index 1c22bac..001cbc9 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,27 +1,43 @@
# 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
+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
+
+ARG UID=61000
+ARG GID=61000
+
+RUN groupadd \
+ --system --gid $GID \
+ app && \
+ useradd \
+ --system --gid $GID --uid $UID \
+ --no-log-init -m -s /bin/false --home-dir /home/app \
+ app
+
+################################################################################
## 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 && \
@@ -39,6 +55,7 @@ RUN apt update && \
libssl-dev \
llvm \
locales \
+ locales \
make \
python-openssl \
tk-dev \
@@ -46,30 +63,48 @@ RUN apt update && \
xz-utils \
zlib1g-dev \
&& \
+ echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen && \
+ dpkg-reconfigure --frontend=noninteractive locales && \
+ update-locale LANG=en_US.UTF-8 && \
mkdir -p /var/app && \
- git clone https://github.com/pyenv/pyenv.git /usr/local/pyenv && \
- cd /usr/local/pyenv && \
+ chown app:app /var/app
+
+
+ENV LANG en_US.UTF-8
+
+USER app
+WORKDIR /home/app
+ENV HOME /home/app
+
+RUN git clone https://github.com/pyenv/pyenv.git $HOME/.pyenv && \
+ cd $HOME/.pyenv && \
git checkout $PYENV_COMMIT
-ENV PYENV_ROOT /usr/local/pyenv
-ENV PATH $PYENV_ROOT/bin:$PATH
+ENV PYENV_ROOT $HOME/.pyenv
+ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
-
-#######################################################################33
+################################################################################
## Python
-#######################################################################33
+################################################################################
RUN pyenv install $PYTHON_VERSION && \
- pyenv global $PYTHON_VERISON
+ pyenv global $PYTHON_VERSION && \
+ pyenv rehash && \
+ pip install --upgrade pip && \
+ pip install pipenv
-RUN ls $PYENV_ROOT/bin/
-#######################################################################33
-## Install Deps
-#######################################################################33
-ADD ./Pipfile ./Pipfile.lock /var/app/
+
+################################################################################
+## Install App Deps
+################################################################################j
WORKDIR /var/app
-RUN pipenv install --python $(which python3)
+COPY ./Pipfile ./Pipfile.lock /var/app/
+RUN pipenv install --python $PYENV_ROOT/shims/python
-#######################################################################33
+################################################################################
## Install App
-#######################################################################33
-ADD ./* /var/app/
+################################################################################
+COPY . /var/app
+
+VOLUME /data
+
+CMD ['pipenv', 'run', 'bin/tvidd']
diff --git a/Makefile b/Makefile
index b462c41..7dfb487 100644
--- a/Makefile
+++ b/Makefile
@@ -7,11 +7,11 @@ default: docker
peinstall:
pipenv install --python $(shell which python3)
-serve:
+develop:
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" .
+ docker build -t sneak/tvid --build-arg UBUNTU_MIRROR="http://ubuntu.datavi.be/ubuntu" .
diff --git a/tvid/db.py b/tvid/db.py
index 4b66f5d..a71a7aa 100644
--- a/tvid/db.py
+++ b/tvid/db.py
@@ -14,3 +14,4 @@ class TV(Base):
displayid = Column(String(20))
lastSeen = Column(DateTime)
target = Column(String(255))
+ memo = Column(String(255))
diff --git a/tvid/server.py b/tvid/server.py
index 4fec629..cf41ef8 100644
--- a/tvid/server.py
+++ b/tvid/server.py
@@ -19,9 +19,9 @@ import urllib.parse
import os
import random
import string
-from datetime import datetime
+from datetime import datetime, timedelta
-VERSION = '0.0.1'
+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')
@@ -46,14 +46,14 @@ def serve():
# pull in models
from .db import TV
- engine = create_engine(DATABASE_URL, echo=True)
+ engine = create_engine(DATABASE_URL, echo=False)
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).
+ engine,
+ SQLBASE.metadata,
+ keyword='db',
+ create=True,
+ commit=True,
use_kwargs=False
)
@@ -63,17 +63,18 @@ def serve():
# cookie'd value (whether preexisting or new).
@app.get('/')
def indexpage():
- c = request.get_cookie("tvid")
+ c = request.get_cookie("displayid")
if c:
# redirect
- redirect('/tv/' + c)
+ return redirect('/tv/' + c)
else:
newid = genRandomTVID()
- response.set_cookie("tvid", newid)
- redirect('/tv/' + newid)
+ 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
@@ -81,10 +82,8 @@ def serve():
# reloading the page once per hour.
@app.get('/tv/')
def tvpage(db, displayid=None):
- # FIXME regex check id to make sure displayid is right format,
- # return error if not
-
- if id is None:
+ # FIXME check for cookie, this is broken
+ if displayid is None:
return template('nocookie')
# check db for tv id
@@ -93,7 +92,7 @@ def serve():
tv.lastSeen = datetime.now()
db.add(tv)
if tv.target:
- redirect(tv.target)
+ return redirect(tv.target)
else:
return template('displayid', id=displayid, version=VERSION)
else:
@@ -105,6 +104,40 @@ def serve():
db.add(newtv)
return template('displayid', id=displayid, version=VERSION)
+
+ @app.get('/admin/edit/')
+ 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
@@ -113,24 +146,19 @@ def serve():
def adminpage(db):
c = request.get_cookie("psk")
if not c:
- redirect('/login')
- return
+ return redirect('/login')
if c != ADMIN_PSK:
- redirect('/logout')
- return
- tvs = db.query(TV).order_by(TV.lastSeen)
+ 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)
- @app.post('/admin')
- def savesettings():
- c = request.get_cookie("psk")
- if not c:
- redirect('/login')
- return
- if c != ADMIN_PSK:
- redirect('/logout')
- return
- raise NotImplementedError()
# here we ask for a password:
@app.get('/login')
@@ -142,25 +170,22 @@ def serve():
def checklogin():
attemptedPass = request.forms.get('password')
if not attemptedPass:
- redirect(
+ return redirect(
'/login?msg=' +
urllib.parse.quote_plus(u"Incorrect password.")
)
- return
if attemptedPass != ADMIN_PSK:
- redirect(
+ return redirect(
'/login?msg=' +
urllib.parse.quote_plus(u"Incorrect password.")
)
- return
# password is right, cookie them:
response.set_cookie("psk", attemptedPass)
- redirect('/admin')
- return
+ return redirect('/admin')
@app.get('/logout')
def logout():
response.set_cookie("psk", "")
- redirect('/login')
+ return redirect('/login')
app.run(host='0.0.0.0', port=PORT, debug=DEBUG)
diff --git a/views/adminpagebase.tpl b/views/adminpagebase.tpl
new file mode 100644
index 0000000..1dbae05
--- /dev/null
+++ b/views/adminpagebase.tpl
@@ -0,0 +1,17 @@
+% rebase('base.tpl', refresh=None, title='tvid administration')
+
+ TVID Admin
+
+
+
+
+
+
+
+ {{!base}}
+
Powered by tvid v{{version}}
+
diff --git a/views/adminpanel.tpl b/views/adminpanel.tpl
index a8aa0c8..4f8acc9 100644
--- a/views/adminpanel.tpl
+++ b/views/adminpanel.tpl
@@ -1,4 +1,27 @@
-% rebase('base.tpl', refresh=None, title='tvid administration')
+% rebase('adminpagebase.tpl', refresh=None, title='tvid administration')
+TVs
+
+
+
+ Display ID
+ Descriptive Memo
+ Last Seen
+ Target URL
+ Edit
+
+
+
-Powered by tvid v{{version}}
+% for tv in tvs:
+
+ {{tv.displayid}}
+ {{tv.memo or '(none)'}}
+ {{tv.lastSeen}}
+ {{tv.target}}
+ Edit
+
+% end
+
+
+
diff --git a/views/base.tpl b/views/base.tpl
index dc302f5..3e9bf9e 100644
--- a/views/base.tpl
+++ b/views/base.tpl
@@ -7,8 +7,11 @@
% if defined('refresh'):
% end
+
% include('htmlheader.tpl')
-
+
+
+
{{!base}}
diff --git a/views/displayeditform.tpl b/views/displayeditform.tpl
new file mode 100644
index 0000000..b8c38fe
--- /dev/null
+++ b/views/displayeditform.tpl
@@ -0,0 +1,33 @@
+% rebase('adminpagebase.tpl', refresh=None, title='tvid administration')
+
+Edit {{tv.displayid}}
+
+
diff --git a/views/displayid.tpl b/views/displayid.tpl
index d52582d..0ae0d77 100644
--- a/views/displayid.tpl
+++ b/views/displayid.tpl
@@ -4,9 +4,34 @@
Display ID:
{{id}}
-
- (They're only letters. I like India, O like
- Oscar.)
-
- Powered by tvid v{{version}}
+ (Letters only: I like INDIA, O like
+ OSCAR.)
+ Powered by tvid v{{version}}
+
+
diff --git a/views/loginform.tpl b/views/loginform.tpl
index 6b82752..ab57fe4 100644
--- a/views/loginform.tpl
+++ b/views/loginform.tpl
@@ -1,8 +1,6 @@
% rebase('base.tpl', refresh=None, title=None)
-% if defined('msg') and msg:
- {{msg}}
-% end
+
@@ -12,6 +10,14 @@
tvid administration
+ % if defined('msg') and msg:
+
+
{{msg}}
+
+ % end
+
+
+
Powered by tvid v{{version}}
+
+
diff --git a/views/navbar.tpl b/views/navbar.tpl
new file mode 100644
index 0000000..6d45b45
--- /dev/null
+++ b/views/navbar.tpl
@@ -0,0 +1,41 @@
+% rebase('base.tpl', refresh=None, title='tvid administration')
+
+ TVID Admin
+
+
+
+
+
+
+
+
TVs
+
+
+
+
+ Display ID
+ Last Seen
+ Target URL
+ Edit
+
+
+
+
+ % for tv in tvs:
+
+ {{tv.displayid}}
+ {{tv.lastSeen}}
+ {{tv.target}}
+ Edit
+
+ % end
+
+
+
+
+
Powered by tvid v{{version}}
+
diff --git a/views/style.tpl b/views/style.tpl
index 601bbb2..8625691 100644
--- a/views/style.tpl
+++ b/views/style.tpl
@@ -1,12 +1,17 @@
-body {
- font-family: sans-serif;
+#main {
+ background: #fff;
width: 100%;
text-align: center;
- font-size: 20pt
+ font-size: 36pt;
+ margin-top: 5em;
}
-h1 {
- font-size: 48pt;
+#main h1 {
+ font-size: 72pt;
+}
+
+#main small {
+ font-size: 14pt;
}
a {