diff --git a/Dockerfile b/Dockerfile
index 2cf867d..1c22bac 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -59,9 +59,9 @@ ENV PATH $PYENV_ROOT/bin:$PATH
## Python
#######################################################################33
RUN pyenv install $PYTHON_VERSION && \
- pyenv global $PYTHON_VERISON && \
- pip3 install pipenv
+ pyenv global $PYTHON_VERISON
+RUN ls $PYENV_ROOT/bin/
#######################################################################33
## Install Deps
#######################################################################33
diff --git a/Makefile b/Makefile
index ece822b..b462c41 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,6 @@
-export PYTHONPATH =: $(PWD)
+export DOCKER_HOST := ssh://datavi.be
+export PYTHONPATH := $(PWD)
+export SQLITE_FILENAME := ./db.sqlite
default: docker
diff --git a/tvid/server.py b/tvid/server.py
index cad2385..4fec629 100644
--- a/tvid/server.py
+++ b/tvid/server.py
@@ -15,6 +15,7 @@ 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
@@ -25,7 +26,7 @@ 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')
+ADMIN_PSK = os.environ.get('ADMIN_PSK','hunter2')
# sorry for global
SQLBASE = declarative_base()
@@ -109,22 +110,57 @@ def serve():
# 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")
+ def adminpage(db):
+ c = request.get_cookie("psk")
+ if not c:
+ redirect('/login')
+ return
+ if c != ADMIN_PSK:
+ redirect('/logout')
+ return
+ tvs = db.query(TV).order_by(TV.lastSeen)
+ return template('adminpanel', tvs=tvs, version=VERSION)
- # 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():
+ @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()
- #response.set_cookie("adminpw", whatever)
- redirect('/login')
- @app.get('/logut')
+ # 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:
+ redirect(
+ '/login?msg=' +
+ urllib.parse.quote_plus(u"Incorrect password.")
+ )
+ return
+ if attemptedPass != ADMIN_PSK:
+ redirect(
+ '/login?msg=' +
+ urllib.parse.quote_plus(u"Incorrect password.")
+ )
+ return
+ # password is right, cookie them:
+ response.set_cookie("psk", attemptedPass)
+ redirect('/admin')
+ return
+
+ @app.get('/logout')
def logout():
- response.set_cookie("adminpw", "")
+ response.set_cookie("psk", "")
redirect('/login')
app.run(host='0.0.0.0', port=PORT, debug=DEBUG)
diff --git a/views/adminpanel.tpl b/views/adminpanel.tpl
new file mode 100644
index 0000000..a8aa0c8
--- /dev/null
+++ b/views/adminpanel.tpl
@@ -0,0 +1,4 @@
+% rebase('base.tpl', refresh=None, title='tvid administration')
+
+
+
Powered by tvid v{{version}}
diff --git a/views/base.tpl b/views/base.tpl
new file mode 100644
index 0000000..dc302f5
--- /dev/null
+++ b/views/base.tpl
@@ -0,0 +1,16 @@
+
+
+
+ {{ get('title','tvid') }}
+
+
+ % if defined('refresh'):
+
+ % end
+ % include('htmlheader.tpl')
+
+
+
+ {{!base}}
+
+
diff --git a/views/displayid.tpl b/views/displayid.tpl
index 8d3f926..d52582d 100644
--- a/views/displayid.tpl
+++ b/views/displayid.tpl
@@ -1,20 +1,12 @@
-
-
- tv info page
-
-
-
-
-
-
- Display ID:
-
-
{{id}}
+% rebase('base.tpl', refresh='60')
+
+
+ Display ID:
+
+
{{id}}
-
(They're only letters. I like India, O like
- Oscar.)
+
(They're only letters. I like India, O like
+ Oscar.)
-
Powered by tvid v{{version}}
-
-
-
+
Powered by tvid v{{version}}
+
diff --git a/views/htmlheader.tpl b/views/htmlheader.tpl
new file mode 100644
index 0000000..5846f27
--- /dev/null
+++ b/views/htmlheader.tpl
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/views/loginform.tpl b/views/loginform.tpl
new file mode 100644
index 0000000..6b82752
--- /dev/null
+++ b/views/loginform.tpl
@@ -0,0 +1,24 @@
+% rebase('base.tpl', refresh=None, title=None)
+
+% if defined('msg') and msg:
+ {{msg}}
+% end
+
+
+
+
+
+
+
tvid administration
+
+
+
+
+
+
+
+Powered by tvid v{{version}}
diff --git a/views/nocookie.tpl b/views/nocookie.tpl
index a6354c8..64222f0 100644
--- a/views/nocookie.tpl
+++ b/views/nocookie.tpl
@@ -1,10 +1,2 @@
-
-
- tv info page
-
-
-
-
- Please enable cookies, they're required.
-
-
+% rebase('base.tpl', refresh='60')
+Please enable cookies, they're required.
diff --git a/views/style.tpl b/views/style.tpl
index ef0985b..601bbb2 100644
--- a/views/style.tpl
+++ b/views/style.tpl
@@ -8,3 +8,249 @@ body {
h1 {
font-size: 48pt;
}
+
+a {
+ color: #92badd;
+ display:inline-block;
+ text-decoration: none;
+ font-weight: 400;
+}
+
+h2 {
+ text-align: center;
+ font-size: 16px;
+ font-weight: 600;
+ text-transform: uppercase;
+ display:inline-block;
+ margin: 40px 8px 10px 8px;
+ color: #cccccc;
+}
+
+
+/* STRUCTURE */
+
+.wrapper {
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ justify-content: center;
+ width: 100%;
+ min-height: 100%;
+ padding: 20px;
+}
+
+#formContent {
+ -webkit-border-radius: 10px 10px 10px 10px;
+ border-radius: 10px 10px 10px 10px;
+ background: #fff;
+ padding: 30px;
+ width: 90%;
+ max-width: 450px;
+ position: relative;
+ padding: 0px;
+ -webkit-box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
+ box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3);
+ text-align: center;
+}
+
+#formFooter {
+ background-color: #f6f6f6;
+ border-top: 1px solid #dce8f1;
+ padding: 25px;
+ text-align: center;
+ -webkit-border-radius: 0 0 10px 10px;
+ border-radius: 0 0 10px 10px;
+}
+
+
+/* TABS */
+
+h2.inactive {
+ color: #cccccc;
+}
+
+h2.active {
+ color: #0d0d0d;
+ border-bottom: 2px solid #5fbae9;
+}
+
+
+/* FORM TYPOGRAPHY*/
+
+input[type=button], input[type=submit], input[type=reset] {
+ background-color: #56baed;
+ border: none;
+ color: white;
+ padding: 15px 80px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ text-transform: uppercase;
+ font-size: 13px;
+ -webkit-box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
+ box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4);
+ -webkit-border-radius: 5px 5px 5px 5px;
+ border-radius: 5px 5px 5px 5px;
+ margin: 5px 20px 40px 20px;
+ -webkit-transition: all 0.3s ease-in-out;
+ -moz-transition: all 0.3s ease-in-out;
+ -ms-transition: all 0.3s ease-in-out;
+ -o-transition: all 0.3s ease-in-out;
+ transition: all 0.3s ease-in-out;
+}
+
+input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover {
+ background-color: #39ace7;
+}
+
+input[type=button]:active, input[type=submit]:active, input[type=reset]:active {
+ -moz-transform: scale(0.95);
+ -webkit-transform: scale(0.95);
+ -o-transform: scale(0.95);
+ -ms-transform: scale(0.95);
+ transform: scale(0.95);
+}
+
+input[type=text], input[type=password] {
+ background-color: #f6f6f6;
+ border: none;
+ color: #0d0d0d;
+ padding: 15px 32px;
+ text-align: center;
+ text-decoration: none;
+ display: inline-block;
+ font-size: 16px;
+ margin: 5px;
+ width: 85%;
+ border: 2px solid #f6f6f6;
+ -webkit-transition: all 0.5s ease-in-out;
+ -moz-transition: all 0.5s ease-in-out;
+ -ms-transition: all 0.5s ease-in-out;
+ -o-transition: all 0.5s ease-in-out;
+ transition: all 0.5s ease-in-out;
+ -webkit-border-radius: 5px 5px 5px 5px;
+ border-radius: 5px 5px 5px 5px;
+}
+
+input[type=text]:focus {
+ background-color: #fff;
+ border-bottom: 2px solid #5fbae9;
+}
+
+input[type=text]:placeholder {
+ color: #cccccc;
+}
+
+/* ANIMATIONS */
+
+/* Simple CSS3 Fade-in-down Animation */
+.fadeInDown {
+ -webkit-animation-name: fadeInDown;
+ animation-name: fadeInDown;
+ -webkit-animation-duration: 1s;
+ animation-duration: 1s;
+ -webkit-animation-fill-mode: both;
+ animation-fill-mode: both;
+}
+
+@-webkit-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ 100% {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+@keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translate3d(0, -100%, 0);
+ transform: translate3d(0, -100%, 0);
+ }
+ 100% {
+ opacity: 1;
+ -webkit-transform: none;
+ transform: none;
+ }
+}
+
+/* Simple CSS3 Fade-in Animation */
+@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+@keyframes fadeIn { from { opacity:0; } to { opacity:1; } }
+
+.fadeIn {
+ opacity:0;
+ -webkit-animation:fadeIn ease-in 1;
+ -moz-animation:fadeIn ease-in 1;
+ animation:fadeIn ease-in 1;
+
+ -webkit-animation-fill-mode:forwards;
+ -moz-animation-fill-mode:forwards;
+ animation-fill-mode:forwards;
+
+ -webkit-animation-duration:1s;
+ -moz-animation-duration:1s;
+ animation-duration:1s;
+}
+
+.fadeIn.first {
+ -webkit-animation-delay: 0.4s;
+ -moz-animation-delay: 0.4s;
+ animation-delay: 0.4s;
+}
+
+.fadeIn.second {
+ -webkit-animation-delay: 0.6s;
+ -moz-animation-delay: 0.6s;
+ animation-delay: 0.6s;
+}
+
+.fadeIn.third {
+ -webkit-animation-delay: 0.8s;
+ -moz-animation-delay: 0.8s;
+ animation-delay: 0.8s;
+}
+
+.fadeIn.fourth {
+ -webkit-animation-delay: 1s;
+ -moz-animation-delay: 1s;
+ animation-delay: 1s;
+}
+
+/* Simple CSS3 Fade-in Animation */
+.underlineHover:after {
+ display: block;
+ left: 0;
+ bottom: -10px;
+ width: 0;
+ height: 2px;
+ background-color: #56baed;
+ content: "";
+ transition: width 0.2s;
+}
+
+.underlineHover:hover {
+ color: #0d0d0d;
+}
+
+.underlineHover:hover:after{
+ width: 100%;
+}
+
+
+
+/* OTHERS */
+
+*:focus {
+ outline: none;
+}
+
+#icon {
+ width:60%;
+}