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') + + + +
+ {{!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

+ + + + + + + + + + + -

Powered by tvid v{{version}}

+% for tv in tvs: + + + + + + + +% end + + +
Display IDDescriptive MemoLast SeenTarget URLEdit
{{tv.displayid}}{{tv.memo or '(none)'}}{{tv.lastSeen}}{{tv.target}}Edit
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') + + + +
+

TVs

+ + + + + + + + + + + + + % for tv in tvs: + + + + + + + % end + + +
Display IDLast SeenTarget URLEdit
{{tv.displayid}}{{tv.lastSeen}}{{tv.target}}Edit
+ +

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 {