1
0
forked from arti/doors

Add Web skeleton

This commit is contained in:
Arti Zirk
2020-09-12 19:44:33 +03:00
parent 2d6403a2bf
commit aa313497cd
25 changed files with 554 additions and 13 deletions

3
kdoorweb/MANIFEST.in Normal file
View File

@@ -0,0 +1,3 @@
graft update_service/static
graft update_service/views
global-exclude *.py[cod]

31
kdoorweb/README.md Normal file
View 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

View File

@@ -0,0 +1,2 @@
-c requirements.txt
pip-tools

View 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

View File

@@ -0,0 +1 @@
from .web import application

View 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
View 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")

View 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;
}

View 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>

View 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>

View 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
View 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
View File

@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools >= 40.6.0", "wheel"]
build-backend = "setuptools.build_meta"

View 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
View 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'
]
)