forked from arti/doors
Add Web skeleton
This commit is contained in:
3
kdoorweb/MANIFEST.in
Normal file
3
kdoorweb/MANIFEST.in
Normal file
@@ -0,0 +1,3 @@
|
||||
graft update_service/static
|
||||
graft update_service/views
|
||||
global-exclude *.py[cod]
|
31
kdoorweb/README.md
Normal file
31
kdoorweb/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# K-Door-web
|
||||
|
||||
# Development setup
|
||||
|
||||
python3 -m venv venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
## Initialize database
|
||||
|
||||
source venv/bin/activate
|
||||
|
||||
|
||||
|
||||
## Run dev server
|
||||
|
||||
source venv/bin/activate
|
||||
bottle.py --debug --reload kdoorweb:application
|
||||
|
||||
## Run unittests
|
||||
|
||||
source venv/bin/activate
|
||||
python -m unittest discover tests
|
||||
|
||||
## Update requirements.txt
|
||||
|
||||
source venv/bin/activate
|
||||
pip-compile --upgrade setup.py
|
||||
pip-compile --upgrade dev-requirements.in
|
||||
# Apply updates to current venv
|
||||
pip-sync dev-requirements.txt requirements.txt
|
2
kdoorweb/dev-requirements.in
Normal file
2
kdoorweb/dev-requirements.in
Normal file
@@ -0,0 +1,2 @@
|
||||
-c requirements.txt
|
||||
pip-tools
|
12
kdoorweb/dev-requirements.txt
Normal file
12
kdoorweb/dev-requirements.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile dev-requirements.in
|
||||
#
|
||||
click==7.1.2 # via pip-tools
|
||||
pip-tools==5.3.1 # via -r dev-requirements.in
|
||||
six==1.15.0 # via pip-tools
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# pip
|
1
kdoorweb/kdoorweb/__init__.py
Normal file
1
kdoorweb/kdoorweb/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .web import application
|
9
kdoorweb/kdoorweb/__main__.py
Normal file
9
kdoorweb/kdoorweb/__main__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
import sys
|
||||
from . import application
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) > 1:
|
||||
port = int(sys.argv[1])
|
||||
else:
|
||||
port = 8080
|
||||
application.run(host='127.0.0.1', port=port)
|
93
kdoorweb/kdoorweb/db.py
Normal file
93
kdoorweb/kdoorweb/db.py
Normal file
@@ -0,0 +1,93 @@
|
||||
import datetime
|
||||
import sqlite3
|
||||
|
||||
SCHEMA_VERSION = 1
|
||||
|
||||
|
||||
class DB:
|
||||
|
||||
def __init__(self, dbfile=":memory:"):
|
||||
self.dbfile = dbfile
|
||||
self.db = sqlite3.connect(self.dbfile)
|
||||
|
||||
@staticmethod
|
||||
def create_db(dbfile):
|
||||
db = sqlite3.connect(dbfile)
|
||||
db.executescript("""
|
||||
create table versions (
|
||||
version integer,
|
||||
upgraded text
|
||||
);
|
||||
create table users (
|
||||
id integer primary key,
|
||||
user text,
|
||||
real_name text,
|
||||
email text,
|
||||
disabled integer,
|
||||
admin integer
|
||||
);
|
||||
create table keycards (
|
||||
id integer primary key,
|
||||
user_id integer,
|
||||
card_uid blob,
|
||||
name text,
|
||||
created text,
|
||||
|
||||
foreign key (user_id)
|
||||
references users (id)
|
||||
on delete cascade
|
||||
);
|
||||
create table doors (
|
||||
id integer primary key,
|
||||
name text,
|
||||
note text,
|
||||
api_key text,
|
||||
created text,
|
||||
disabled integer
|
||||
);
|
||||
create table door_log (
|
||||
id integer primary key,
|
||||
timestamp integer,
|
||||
door_id integer,
|
||||
keycard_id integer,
|
||||
user_id integer,
|
||||
|
||||
foreign key (door_id)
|
||||
references doors (id)
|
||||
on delete set null,
|
||||
foreign key (user_id)
|
||||
references users (id)
|
||||
on delete set null,
|
||||
foreign key (keycard_id)
|
||||
references keycards (id)
|
||||
on delete set null
|
||||
)
|
||||
""")
|
||||
db.execute(
|
||||
"insert into versions (version, upgraded) values (?, ?)",
|
||||
(SCHEMA_VERSION, str(datetime.datetime.now()))
|
||||
)
|
||||
db.commit()
|
||||
|
||||
def list_users(*, page=0, count=20, query=None):
|
||||
pass
|
||||
|
||||
def add_user(self, user, real_name=None, email=None, disabled=False, admin=False):
|
||||
self.add_users([(
|
||||
user, real_name, email, disabled, admin
|
||||
)])
|
||||
|
||||
def add_users(self, users):
|
||||
self.db.executemany("""
|
||||
insert into users(user, real_name, email, disabled, admin)
|
||||
values(?, ?, ?, ?, ?)
|
||||
""", users)
|
||||
self.db.commit()
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#DB.create_db("kdoorweb.sqlite")
|
||||
db = DB("kdoorweb.sqlite")
|
||||
db.add_user("juku")
|
152
kdoorweb/kdoorweb/static/artistyle.css
Normal file
152
kdoorweb/kdoorweb/static/artistyle.css
Normal file
@@ -0,0 +1,152 @@
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
*, *:before, *:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 40px auto;
|
||||
max-width: 650px;
|
||||
line-height: 1.6;
|
||||
font-size: 18px;
|
||||
color: #444;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
h1, h2, h3 {
|
||||
line-height: 1.2
|
||||
}
|
||||
|
||||
hr {
|
||||
display: block;
|
||||
height: 1px;
|
||||
border: 0;
|
||||
border-top: 1px solid #ccc;
|
||||
margin: 1em 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.page-header h1, .page-header h2, .page-header h3{
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
td, th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.even tr:nth-child(even) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.double tr:nth-child(4n+0) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.double tr:nth-child(4n+3) {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.double th:first-child {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.flash {
|
||||
margin: 1em 0;
|
||||
padding: 1em;
|
||||
background: #f6e7db;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
.errors {
|
||||
margin: 0.1em;
|
||||
padding: 0.1em 0.5em;
|
||||
background: #ffb3b3;
|
||||
border: 1px solid #000000;
|
||||
flex: 100%
|
||||
}
|
||||
ul.errors {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Top-down Form
|
||||
Label [Input]
|
||||
-----Button-----
|
||||
*/
|
||||
|
||||
form {
|
||||
display:flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form label {
|
||||
flex-basis: 30%;
|
||||
}
|
||||
|
||||
form input {
|
||||
flex-basis: 70%;
|
||||
margin: 0.4em 0;
|
||||
height: 2em;
|
||||
}
|
||||
|
||||
form input[type=submit], form input[type=button] {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
form .basis_auto {
|
||||
flex-basis: auto !important;
|
||||
}
|
||||
|
||||
form textarea {
|
||||
height: 30em;
|
||||
flex-basis: 100%;
|
||||
margin-bottom:0.4em;
|
||||
}
|
||||
|
||||
/* Inline Form
|
||||
Label [Input] Button
|
||||
*/
|
||||
form.inline {
|
||||
flex-wrap: nowrap;
|
||||
align-content: stretch;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form.inline label, form.inline input {
|
||||
align-self: unset;
|
||||
flex: 1 1 auto;
|
||||
margin: 0 0.3em 1em;
|
||||
}
|
||||
|
||||
form.inline label,
|
||||
form.inline input[type=submit],
|
||||
form.inline input[type=reset],
|
||||
form.inline input[type=button] {
|
||||
flex: 0 1 auto;
|
||||
}
|
23
kdoorweb/kdoorweb/views/base.html
Normal file
23
kdoorweb/kdoorweb/views/base.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="stylesheet" href="/static/artistyle.css">
|
||||
<title>K-Space Door system</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>K-Space Door system</h1>
|
||||
<nav>
|
||||
<a href="/list">Users List</a>
|
||||
<a href="/log">Door Log</a>
|
||||
<a href="/logout">Logout</a>
|
||||
</nav>
|
||||
</header>
|
||||
<hr>
|
||||
|
||||
{{!base}}
|
||||
|
||||
</body>
|
||||
</html>
|
20
kdoorweb/kdoorweb/views/list.html
Normal file
20
kdoorweb/kdoorweb/views/list.html
Normal file
@@ -0,0 +1,20 @@
|
||||
% rebase('base.html')
|
||||
|
||||
<h3>Users List</h3>
|
||||
|
||||
<table class="even">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>nr of cards</th>
|
||||
<th>Last access</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><a href="/info/1">Arti Zirk</a></td>
|
||||
<td>2</td>
|
||||
<td>-</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
15
kdoorweb/kdoorweb/views/login.html
Normal file
15
kdoorweb/kdoorweb/views/login.html
Normal file
@@ -0,0 +1,15 @@
|
||||
% rebase('base.html')
|
||||
|
||||
% if error:
|
||||
<p>Error: {{ error }}</p>
|
||||
% end
|
||||
|
||||
<form action="" method="post">
|
||||
<label for="user">User</label>
|
||||
<input type="text" id="user" name="user">
|
||||
|
||||
<label for="password">Password</label>
|
||||
<input type="password" id="password" name="password">
|
||||
|
||||
<input type="submit" value="Login">
|
||||
</form>
|
79
kdoorweb/kdoorweb/web.py
Normal file
79
kdoorweb/kdoorweb/web.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import os
|
||||
import secrets
|
||||
|
||||
from bottle import Bottle, view, TEMPLATE_PATH, static_file, \
|
||||
request, redirect, response
|
||||
|
||||
application = app = Bottle()
|
||||
|
||||
# TODO: Could be replaced with importlib.resources
|
||||
TEMPLATE_PATH.append(os.path.join(os.path.dirname(__file__), "views"))
|
||||
STATIC_PATH = os.path.join(os.path.dirname(__file__), "static")
|
||||
|
||||
COOKIE_KEY = secrets.token_bytes(32)
|
||||
|
||||
|
||||
def check_auth(callback):
|
||||
def wrapper(*args, **kwargs):
|
||||
user = request.get_cookie("user", key=COOKIE_KEY)
|
||||
if user:
|
||||
print(f"logged in as {user}")
|
||||
return callback(*args, **kwargs)
|
||||
else:
|
||||
print("not logged in")
|
||||
response.set_cookie("error", "Not logged in")
|
||||
redirect("/login")
|
||||
return wrapper
|
||||
|
||||
|
||||
app.install(check_auth)
|
||||
|
||||
|
||||
@app.route('/static/<path:path>', skip=[check_auth])
|
||||
def callback(path):
|
||||
return static_file(path, root=STATIC_PATH)
|
||||
|
||||
|
||||
@app.route("/", skip=[check_auth])
|
||||
def index():
|
||||
redirect("/login")
|
||||
|
||||
|
||||
@app.route('/login', skip=[check_auth])
|
||||
@view("login.html")
|
||||
def login():
|
||||
error = request.get_cookie("error")
|
||||
if error:
|
||||
response.set_cookie("error", "")
|
||||
return {"error": error}
|
||||
|
||||
|
||||
@app.post('/login', skip=[check_auth])
|
||||
def do_login():
|
||||
print(dict(request.forms))
|
||||
user = request.forms.get("user")
|
||||
print(f"user {user}")
|
||||
if user:
|
||||
response.set_cookie("user", user, key=COOKIE_KEY)
|
||||
redirect("/list")
|
||||
else:
|
||||
response.set_cookie("error", "No user")
|
||||
redirect("/login")
|
||||
|
||||
|
||||
@app.route("/logout")
|
||||
def logout():
|
||||
response.set_cookie("user", "", key=COOKIE_KEY)
|
||||
redirect("/login")
|
||||
|
||||
|
||||
@app.route("/list")
|
||||
@view("list.html")
|
||||
def list():
|
||||
return {}
|
||||
|
||||
|
||||
@app.route("/log")
|
||||
@view("log")
|
||||
def log():
|
||||
return {}
|
3
kdoorweb/pyproject.toml
Normal file
3
kdoorweb/pyproject.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools >= 40.6.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
7
kdoorweb/requirements.txt
Normal file
7
kdoorweb/requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile
|
||||
# To update, run:
|
||||
#
|
||||
# pip-compile setup.py
|
||||
#
|
||||
bottle==0.12.18 # via kdoorweb (setup.py)
|
26
kdoorweb/setup.py
Normal file
26
kdoorweb/setup.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name='kdoorweb',
|
||||
version='0.0.0',
|
||||
author="Arti Zirk",
|
||||
author_email="arti@zirk.me",
|
||||
description="K-Space Door Administraion Web Interface",
|
||||
packages=find_packages(),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
python_requires='>=3.6',
|
||||
install_requires=[
|
||||
'bottle'
|
||||
],
|
||||
classifiers=[
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
|
||||
'Topic :: System :: Networking',
|
||||
'Intended Audience :: System Administrators',
|
||||
'Framework :: Bottle'
|
||||
]
|
||||
|
||||
)
|
Reference in New Issue
Block a user