mirror of
http://git.k-space.ee/arti/doors.git
synced 2024-11-12 15:30:59 +02:00
Add Web skeleton
This commit is contained in:
parent
2d6403a2bf
commit
aa313497cd
24
.editorconfig
Normal file
24
.editorconfig
Normal 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
6
.gitignore
vendored
@ -58,3 +58,9 @@ docs/_build/
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Other
|
||||
venv/
|
||||
*.sqlite
|
||||
|
2
LICENSE
2
LICENSE
@ -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:
|
||||
|
||||
|
@ -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
|
10
doors/db.py
10
doors/db.py
@ -1,10 +0,0 @@
|
||||
import sqlite3
|
||||
|
||||
|
||||
class DB:
|
||||
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
|
||||
|
||||
|
15
kdoorpi/README.md
Normal file
15
kdoorpi/README.md
Normal 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
3
kdoorpi/pyproject.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[build-system]
|
||||
requires = ["setuptools >= 40.6.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
23
kdoorpi/setup.py
Normal file
23
kdoorpi/setup.py
Normal 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
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'
|
||||
]
|
||||
|
||||
)
|
Loading…
Reference in New Issue
Block a user