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

24
.editorconfig Normal file
View File

@ -0,0 +1,24 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
[*.html]
indent_style = space
indent_size = 2

6
.gitignore vendored
View File

@ -58,3 +58,9 @@ docs/_build/
# PyBuilder
target/
# PyCharm
.idea/
# Other
venv/
*.sqlite

View File

@ -1,5 +1,5 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2020 Arti Zirk <arti@zirk.me>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -1,3 +1,7 @@
# doors
# K-Doors
[K-Space](https://k-space.ee) door system. This repo contains two sub projects:
* [K-Door-Web](kdoorweb) - Central door admin web interface
* [K-Door-Pi](kdoorpi) - Door client that runs on Raspberry Pi and talks to the hardware.
K-Space door system

View File

View File

@ -1,10 +0,0 @@
import sqlite3
class DB:
def __init__(self, path):
self.path = path

15
kdoorpi/README.md Normal file
View File

@ -0,0 +1,15 @@
# K-Door-Pi
Client for K-Space door system. Run on Raspberry Pi and controlls the doors
# Requirements
This project needs at least Linux kernel 4.8 and uses crossplatform [libgpiod]
to talk to the hardware. Under Debian/Ubuntu you have to install [python3-libgpiod].
sudo apt install python3-libgpiod
[libgpiod]: https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/about/
[python3-libgpiod]: https://packages.debian.org/sid/python3-libgpiod

3
kdoorpi/pyproject.toml Normal file
View File

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

23
kdoorpi/setup.py Normal file
View File

@ -0,0 +1,23 @@
from setuptools import find_packages, setup
setup(
name='kdoorpi',
version='0.0.0',
author="Arti Zirk",
author_email="arti@zirk.me",
description="K-Space Door client that talks to the hardware",
packages=find_packages(),
include_package_data=True,
zip_safe=False,
python_requires='>=3.5',
install_requires=[],
extras_require={},
classifiers=[
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3 :: Only',
'Topic :: System :: Networking',
'Intended Audience :: System Administrators',
]
)

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