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