1.0.0 beta
This commit is contained in:
parent
691d54ee3e
commit
79e1f6e9e3
|
@ -1,3 +1,4 @@
|
||||||
dist/
|
dist/
|
||||||
db.sqlite
|
db.sqlite
|
||||||
build/
|
build/
|
||||||
|
.git
|
||||||
|
|
92
Dockerfile
92
Dockerfile
|
@ -1,27 +1,43 @@
|
||||||
# focal amd64 as of 2020-03-10
|
# focal amd64 as of 2020-03-10
|
||||||
FROM ubuntu@sha256:d050ed7278c16ff627e4a70d7d353f1a2ec74d8a0b66e5a865356d92f5f6d87b
|
FROM ubuntu@sha256:d050ed7278c16ff627e4a70d7d353f1a2ec74d8a0b66e5a865356d92f5f6d87b
|
||||||
|
|
||||||
#######################################################################33
|
################################################################################
|
||||||
## Mirror Setup
|
## Mirror Setup
|
||||||
## - option to use local mirror to speed build
|
## - option to use local mirror to speed build
|
||||||
#######################################################################33
|
################################################################################
|
||||||
ARG UBUNTU_MIRROR=http://archive.ubuntu.com/ubuntu
|
ARG UBUNTU_MIRROR=http://archive.ubuntu.com/ubuntu
|
||||||
RUN echo "deb $UBUNTU_MIRROR focal main universe restricted multiverse" > /etc/apt/sources.list.new && \
|
RUN echo "deb $UBUNTU_MIRROR focal main universe restricted multiverse" > \
|
||||||
echo "deb $UBUNTU_MIRROR focal-updates main universe restricted multiverse" >> /etc/apt/sources.list.new && \
|
/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-updates main universe restricted multiverse" >> \
|
||||||
echo "deb $UBUNTU_MIRROR focal-backports main universe restricted multiverse" >> /etc/apt/sources.list.new && \
|
/etc/apt/sources.list.new && \
|
||||||
mv /etc/apt/sources.list.new /etc/apt/sources.list
|
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
|
## Versions
|
||||||
#######################################################################33
|
################################################################################
|
||||||
# master as of 2020-03-10
|
# master as of 2020-03-10
|
||||||
ARG PYENV_COMMIT=df9fa1dc30b6448ef8605e2c2d4dfc2a94d6a35d
|
ARG PYENV_COMMIT=df9fa1dc30b6448ef8605e2c2d4dfc2a94d6a35d
|
||||||
ARG PYTHON_VERSION=3.8.1
|
ARG PYTHON_VERSION=3.8.1
|
||||||
|
|
||||||
#######################################################################33
|
################################################################################
|
||||||
## Packages
|
## Packages
|
||||||
#######################################################################33
|
################################################################################
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
RUN apt update && \
|
RUN apt update && \
|
||||||
apt upgrade -y && \
|
apt upgrade -y && \
|
||||||
|
@ -39,6 +55,7 @@ RUN apt update && \
|
||||||
libssl-dev \
|
libssl-dev \
|
||||||
llvm \
|
llvm \
|
||||||
locales \
|
locales \
|
||||||
|
locales \
|
||||||
make \
|
make \
|
||||||
python-openssl \
|
python-openssl \
|
||||||
tk-dev \
|
tk-dev \
|
||||||
|
@ -46,30 +63,49 @@ RUN apt update && \
|
||||||
xz-utils \
|
xz-utils \
|
||||||
zlib1g-dev \
|
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 && \
|
mkdir -p /var/app && \
|
||||||
git clone https://github.com/pyenv/pyenv.git /usr/local/pyenv && \
|
chown app:app /var/app
|
||||||
cd /usr/local/pyenv && \
|
|
||||||
|
|
||||||
|
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
|
git checkout $PYENV_COMMIT
|
||||||
|
|
||||||
ENV PYENV_ROOT /usr/local/pyenv
|
ENV PYENV_ROOT $HOME/.pyenv
|
||||||
ENV PATH $PYENV_ROOT/bin:$PATH
|
ENV PATH $PYENV_ROOT/shims:$PYENV_ROOT/bin:$PATH
|
||||||
|
|
||||||
|
################################################################################
|
||||||
#######################################################################33
|
|
||||||
## Python
|
## Python
|
||||||
#######################################################################33
|
################################################################################
|
||||||
RUN pyenv install $PYTHON_VERSION && \
|
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
|
## Install App Deps
|
||||||
#######################################################################33
|
################################################################################j
|
||||||
ADD ./Pipfile ./Pipfile.lock /var/app/
|
|
||||||
WORKDIR /var/app
|
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
|
## Install App
|
||||||
#######################################################################33
|
################################################################################
|
||||||
ADD ./* /var/app/
|
COPY . /var/app
|
||||||
|
|
||||||
|
VOLUME /data
|
||||||
|
|
||||||
|
ENV PYTHONPATH /var/app
|
||||||
|
CMD pipenv run python ./bin/tvidd
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
Version 2, December 2004
|
||||||
|
|
||||||
|
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||||
|
|
||||||
|
Everyone is permitted to copy and distribute verbatim or modified
|
||||||
|
copies of this license document, and changing it is allowed as long
|
||||||
|
as the name is changed.
|
||||||
|
|
||||||
|
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||||
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||||
|
|
||||||
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||||
|
|
4
Makefile
4
Makefile
|
@ -7,11 +7,11 @@ default: docker
|
||||||
peinstall:
|
peinstall:
|
||||||
pipenv install --python $(shell which python3)
|
pipenv install --python $(shell which python3)
|
||||||
|
|
||||||
serve:
|
develop:
|
||||||
pipenv run python ./bin/tvidd
|
pipenv run python ./bin/tvidd
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -rf build dist tvid.egg-info
|
rm -rf build dist tvid.egg-info
|
||||||
|
|
||||||
docker:
|
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" .
|
||||||
|
|
23
README.md
23
README.md
|
@ -9,9 +9,30 @@ 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
|
seconds, that display will bounce to that URL, or any other time it turns
|
||||||
on.
|
on.
|
||||||
|
|
||||||
|
You can reconfigure the target URL at any time, and the next time that
|
||||||
|
display reboots or reloads (you should be rebooting your displays daily) it
|
||||||
|
will get redirected to the new target.
|
||||||
|
|
||||||
|
# configuration knobs
|
||||||
|
|
||||||
|
## environment variables
|
||||||
|
|
||||||
|
* set `ADMIN_PSK` to the admin password (for `/admin` url)
|
||||||
|
|
||||||
|
## state storage
|
||||||
|
|
||||||
|
* writes sqlite database into `/data`, mount that volume somewhere
|
||||||
|
|
||||||
# todo
|
# todo
|
||||||
|
|
||||||
* make display id animate: https://codepen.io/stezu/pen/cmLrI
|
* fix CSRF
|
||||||
|
* fix FIXMEs
|
||||||
|
* fix logging output
|
||||||
|
* put git short id into version string
|
||||||
|
|
||||||
|
# license
|
||||||
|
|
||||||
|
WTFPL
|
||||||
|
|
||||||
# author
|
# author
|
||||||
|
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -1,8 +1,11 @@
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
|
# for some reason that console_scripts entrypoint fails
|
||||||
|
# and i have no idea why so there is a bin in bin/
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='tvid',
|
name='tvid',
|
||||||
version='0.0.1',
|
version='1.0.0',
|
||||||
packages=find_packages(),
|
packages=find_packages(),
|
||||||
license='WTFPL',
|
license='WTFPL',
|
||||||
long_description=open('README.md').read(),
|
long_description=open('README.md').read(),
|
||||||
|
|
|
@ -14,3 +14,4 @@ class TV(Base):
|
||||||
displayid = Column(String(20))
|
displayid = Column(String(20))
|
||||||
lastSeen = Column(DateTime)
|
lastSeen = Column(DateTime)
|
||||||
target = Column(String(255))
|
target = Column(String(255))
|
||||||
|
memo = Column(String(255))
|
||||||
|
|
103
tvid/server.py
103
tvid/server.py
|
@ -19,9 +19,9 @@ import urllib.parse
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
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)
|
PORT = os.environ.get('PORT', 8080)
|
||||||
DEBUG = os.environ.get('DEBUG', False)
|
DEBUG = os.environ.get('DEBUG', False)
|
||||||
SQLITE_FILENAME = os.environ.get('SQLITE_FILENAME','/data/db.sqlite')
|
SQLITE_FILENAME = os.environ.get('SQLITE_FILENAME','/data/db.sqlite')
|
||||||
|
@ -46,14 +46,14 @@ def serve():
|
||||||
# pull in models
|
# pull in models
|
||||||
from .db import TV
|
from .db import TV
|
||||||
|
|
||||||
engine = create_engine(DATABASE_URL, echo=True)
|
engine = create_engine(DATABASE_URL, echo=False)
|
||||||
|
|
||||||
plugin = sqlalchemy.Plugin(
|
plugin = sqlalchemy.Plugin(
|
||||||
engine, # SQLAlchemy engine created with create_engine function.
|
engine,
|
||||||
SQLBASE.metadata, # SQLAlchemy metadata, required only if create=True.
|
SQLBASE.metadata,
|
||||||
keyword='db', # Keyword used to inject session database in a route (default 'db').
|
keyword='db',
|
||||||
create=True, # If it is true, execute `metadata.create_all(engine)` when plugin is applied (default False).
|
create=True,
|
||||||
commit=True, # If it is true, plugin commit changes after route is executed (default True).
|
commit=True,
|
||||||
use_kwargs=False
|
use_kwargs=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -63,17 +63,18 @@ def serve():
|
||||||
# cookie'd value (whether preexisting or new).
|
# cookie'd value (whether preexisting or new).
|
||||||
@app.get('/')
|
@app.get('/')
|
||||||
def indexpage():
|
def indexpage():
|
||||||
c = request.get_cookie("tvid")
|
c = request.get_cookie("displayid")
|
||||||
if c:
|
if c:
|
||||||
# redirect
|
# redirect
|
||||||
redirect('/tv/' + c)
|
return redirect('/tv/' + c)
|
||||||
else:
|
else:
|
||||||
newid = genRandomTVID()
|
newid = genRandomTVID()
|
||||||
response.set_cookie("tvid", newid)
|
response.set_cookie("displayid", newid)
|
||||||
redirect('/tv/' + newid)
|
return redirect('/tv/' + newid)
|
||||||
|
|
||||||
@app.get('/style.css')
|
@app.get('/style.css')
|
||||||
def stylesheet():
|
def stylesheet():
|
||||||
|
response.content_type = 'text/css'
|
||||||
return template('style')
|
return template('style')
|
||||||
|
|
||||||
# here we check to see if they have a redirect URL in the db. if they do
|
# 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.
|
# reloading the page once per hour.
|
||||||
@app.get('/tv/<displayid>')
|
@app.get('/tv/<displayid>')
|
||||||
def tvpage(db, displayid=None):
|
def tvpage(db, displayid=None):
|
||||||
# FIXME regex check id to make sure displayid is right format,
|
# FIXME check for cookie, this is broken
|
||||||
# return error if not
|
if displayid is None:
|
||||||
|
|
||||||
if id is None:
|
|
||||||
return template('nocookie')
|
return template('nocookie')
|
||||||
|
|
||||||
# check db for tv id
|
# check db for tv id
|
||||||
|
@ -93,7 +92,7 @@ def serve():
|
||||||
tv.lastSeen = datetime.now()
|
tv.lastSeen = datetime.now()
|
||||||
db.add(tv)
|
db.add(tv)
|
||||||
if tv.target:
|
if tv.target:
|
||||||
redirect(tv.target)
|
return redirect(tv.target)
|
||||||
else:
|
else:
|
||||||
return template('displayid', id=displayid, version=VERSION)
|
return template('displayid', id=displayid, version=VERSION)
|
||||||
else:
|
else:
|
||||||
|
@ -105,6 +104,40 @@ def serve():
|
||||||
db.add(newtv)
|
db.add(newtv)
|
||||||
return template('displayid', id=displayid, version=VERSION)
|
return template('displayid', id=displayid, version=VERSION)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/admin/edit/<displayid>')
|
||||||
|
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
|
# here we display the administration list of TVs if logged in
|
||||||
# if logged out then redirect to /login
|
# if logged out then redirect to /login
|
||||||
# FIXME make this use sessions instead of just storing PSK in a cookie
|
# FIXME make this use sessions instead of just storing PSK in a cookie
|
||||||
|
@ -113,24 +146,19 @@ def serve():
|
||||||
def adminpage(db):
|
def adminpage(db):
|
||||||
c = request.get_cookie("psk")
|
c = request.get_cookie("psk")
|
||||||
if not c:
|
if not c:
|
||||||
redirect('/login')
|
return redirect('/login')
|
||||||
return
|
|
||||||
if c != ADMIN_PSK:
|
if c != ADMIN_PSK:
|
||||||
redirect('/logout')
|
return redirect('/logout')
|
||||||
return
|
|
||||||
tvs = db.query(TV).order_by(TV.lastSeen)
|
# 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)
|
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:
|
# here we ask for a password:
|
||||||
@app.get('/login')
|
@app.get('/login')
|
||||||
|
@ -142,25 +170,22 @@ def serve():
|
||||||
def checklogin():
|
def checklogin():
|
||||||
attemptedPass = request.forms.get('password')
|
attemptedPass = request.forms.get('password')
|
||||||
if not attemptedPass:
|
if not attemptedPass:
|
||||||
redirect(
|
return redirect(
|
||||||
'/login?msg=' +
|
'/login?msg=' +
|
||||||
urllib.parse.quote_plus(u"Incorrect password.")
|
urllib.parse.quote_plus(u"Incorrect password.")
|
||||||
)
|
)
|
||||||
return
|
|
||||||
if attemptedPass != ADMIN_PSK:
|
if attemptedPass != ADMIN_PSK:
|
||||||
redirect(
|
return redirect(
|
||||||
'/login?msg=' +
|
'/login?msg=' +
|
||||||
urllib.parse.quote_plus(u"Incorrect password.")
|
urllib.parse.quote_plus(u"Incorrect password.")
|
||||||
)
|
)
|
||||||
return
|
|
||||||
# password is right, cookie them:
|
# password is right, cookie them:
|
||||||
response.set_cookie("psk", attemptedPass)
|
response.set_cookie("psk", attemptedPass)
|
||||||
redirect('/admin')
|
return redirect('/admin')
|
||||||
return
|
|
||||||
|
|
||||||
@app.get('/logout')
|
@app.get('/logout')
|
||||||
def logout():
|
def logout():
|
||||||
response.set_cookie("psk", "")
|
response.set_cookie("psk", "")
|
||||||
redirect('/login')
|
return redirect('/login')
|
||||||
|
|
||||||
app.run(host='0.0.0.0', port=PORT, debug=DEBUG)
|
app.run(host='0.0.0.0', port=PORT, debug=DEBUG)
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
% rebase('base.tpl', refresh=None, title='tvid administration')
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
||||||
|
<a class="navbar-brand" href="/admin">TVID Admin</a>
|
||||||
|
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="btn btn-secondary" href="/logout">Log Out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container" id="adminpanel">
|
||||||
|
{{!base}}
|
||||||
|
<p><small>Powered by tvid v{{version}}</small></p>
|
||||||
|
</div>
|
|
@ -1,4 +1,27 @@
|
||||||
% rebase('base.tpl', refresh=None, title='tvid administration')
|
% rebase('adminpagebase.tpl', refresh=None, title='tvid administration')
|
||||||
|
<h1>TVs</h1>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Display ID</th>
|
||||||
|
<th scope="col">Descriptive Memo</th>
|
||||||
|
<th scope="col">Last Seen</th>
|
||||||
|
<th scope="col">Target URL</th>
|
||||||
|
<th scope="col">Edit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
<p><small>Powered by tvid v{{version}}</small></p>
|
% for tv in tvs:
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{tv.displayid}}</th>
|
||||||
|
<td>{{tv.memo or '(none)'}}</td>
|
||||||
|
<td>{{tv.lastSeen}}</td>
|
||||||
|
<td>{{tv.target}}</td>
|
||||||
|
<td><a href="/admin/edit/{{tv.displayid}}" class="btn btn-success btn-sm">Edit</a></td>
|
||||||
|
</tr>
|
||||||
|
% end
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
|
@ -7,8 +7,11 @@
|
||||||
% if defined('refresh'):
|
% if defined('refresh'):
|
||||||
<meta http-equiv="refresh" content="{{ get('refresh',60) }}">
|
<meta http-equiv="refresh" content="{{ get('refresh',60) }}">
|
||||||
% end
|
% end
|
||||||
|
|
||||||
% include('htmlheader.tpl')
|
% include('htmlheader.tpl')
|
||||||
<link rel="stylesheet" href="/style.css" />
|
|
||||||
|
<link rel="stylesheet" href="/style.css" type="text/css">
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{!base}}
|
{{!base}}
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
% rebase('adminpagebase.tpl', refresh=None, title='tvid administration')
|
||||||
|
|
||||||
|
<h1>Edit {{tv.displayid}}</h1>
|
||||||
|
|
||||||
|
<div class="container" style="width: 50%">
|
||||||
|
<form action="/admin/edit" method="post">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="displayid">Display ID</label>
|
||||||
|
<input type="text" class="form-control" id="displayid"
|
||||||
|
value="{{tv.displayid}}"
|
||||||
|
name="displayid"
|
||||||
|
placeholder="{{tv.displayid}}"
|
||||||
|
readonly>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="formmemo">Descriptive Memo</label>
|
||||||
|
<input type="text" class="form-control" id="formmemo"
|
||||||
|
value="{{tv.memo}}"
|
||||||
|
name="formmemo"
|
||||||
|
placeholder="description/location"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="targeturl">Target URL</label>
|
||||||
|
<input type="text" class="form-control" id="targeturl" name="target" placeholder="https://example.com">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -4,9 +4,34 @@
|
||||||
<i>Display ID:</i>
|
<i>Display ID:</i>
|
||||||
</p>
|
</p>
|
||||||
<h1>{{id}}</h1>
|
<h1>{{id}}</h1>
|
||||||
|
<small>(Letters only: I like INDIA, O like
|
||||||
<small>(They're only letters. I like India, O like
|
OSCAR.)<br/>
|
||||||
Oscar.)</small>
|
Powered by tvid v{{version}}</small>
|
||||||
|
|
||||||
<p><small>Powered by tvid v{{version}}</small></p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var x = 0;
|
||||||
|
var factor = 1;
|
||||||
|
function animate() {
|
||||||
|
console.log("animate called");
|
||||||
|
var elem = document.getElementById("main");
|
||||||
|
console.log(elem);
|
||||||
|
console.log(elem.style);
|
||||||
|
elem.style.marginTop = x + 'px';
|
||||||
|
x = x + factor;
|
||||||
|
if(x > 1080) {
|
||||||
|
factor = -1;
|
||||||
|
}
|
||||||
|
if(x < 0) {
|
||||||
|
factor = 1;
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
var b = document.getElementsByTagName("BODY")[0]
|
||||||
|
b.style.background = '#666666';
|
||||||
|
console.log("starting...");
|
||||||
|
setInterval(animate, 20);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
% rebase('base.tpl', refresh=None, title=None)
|
% rebase('base.tpl', refresh=None, title=None)
|
||||||
|
|
||||||
% if defined('msg') and msg:
|
<div class="container">
|
||||||
<div class="card card-body bg-light">{{msg}}</div>
|
|
||||||
% end
|
|
||||||
|
|
||||||
<div class="wrapper fadeInDown">
|
<div class="wrapper fadeInDown">
|
||||||
<div id="formContent">
|
<div id="formContent">
|
||||||
|
@ -12,6 +10,14 @@
|
||||||
<h2>tvid administration</h2>
|
<h2>tvid administration</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
% if defined('msg') and msg:
|
||||||
|
<p>
|
||||||
|
<div class="card card-body bg-light">{{msg}}</div>
|
||||||
|
</p>
|
||||||
|
% end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Login Form -->
|
<!-- Login Form -->
|
||||||
<form action="/checklogin" method="post">
|
<form action="/checklogin" method="post">
|
||||||
<input type="password" id="password" class="fadeIn third"
|
<input type="password" id="password" class="fadeIn third"
|
||||||
|
@ -22,3 +28,5 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p><small>Powered by tvid v{{version}}</small></p>
|
<p><small>Powered by tvid v{{version}}</small></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
% rebase('base.tpl', refresh=None, title='tvid administration')
|
||||||
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
|
||||||
|
<a class="navbar-brand" href="/admin">TVID Admin</a>
|
||||||
|
|
||||||
|
<ul class="navbar-nav ml-auto">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="btn btn-secondary" href="/logout">Log Out</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="container" id="adminpanel">
|
||||||
|
<h1>TVs</h1>
|
||||||
|
|
||||||
|
<table class="table table-striped table-hover">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Display ID</th>
|
||||||
|
<th scope="col">Last Seen</th>
|
||||||
|
<th scope="col">Target URL</th>
|
||||||
|
<th scope="col">Edit</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
% for tv in tvs:
|
||||||
|
<tr>
|
||||||
|
<th scope="row">{{tv.displayid}}</th>
|
||||||
|
<td>{{tv.lastSeen}}</td>
|
||||||
|
<td>{{tv.target}}</td>
|
||||||
|
<td><a href="/admin/edit/{{tv.displayid}}" class="btn btn-success btn-sm">Edit</a></td>
|
||||||
|
</tr>
|
||||||
|
% end
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p><small>Powered by tvid v{{version}}</small></p>
|
||||||
|
</div>
|
|
@ -1,12 +1,17 @@
|
||||||
body {
|
#main {
|
||||||
font-family: sans-serif;
|
background: #fff;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 20pt
|
font-size: 36pt;
|
||||||
|
margin-top: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 {
|
#main h1 {
|
||||||
font-size: 48pt;
|
font-size: 72pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
#main small {
|
||||||
|
font-size: 14pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|
Loading…
Reference in New Issue