Skip to content
Snippets Groups Projects
Commit fa2d8b9d authored by David Huss's avatar David Huss :speech_balloon:
Browse files

More GET options, changed config, better README

parent f9e7e144
Branches
No related tags found
No related merge requests found
......@@ -30,16 +30,27 @@ pip3 install -r requirements.txt
python3 stechuhr_server/server.py
```
Check the output to find the config directory. You might want to run it as a different user tho.
There is also a systemctl unit file that you probably need to change to your needs
In production it makes sense to run stechuhr with gunicorn:
## Deployment
```bash
source env/bin/activate
gunicorn stechuhr_server.server:app
```
The _stechuhr-server_ is meant to run behind a reverse proxy server (e.g. NGINX) and as a systemd service on a Linux system. Gunicorn acts as a runner.
Follow the steps listed above in _Run with python3-ven in production_. To install the stechuhr server. If the software runs in an initial test we need to set up a production environment
1. Create a user called `wwwrun`
2. Copy the systemd unit file `stechuhr-server.service` to `/etc/systemd/system/stechuhr-server.service` and have a look at it. Note the line where it says:
```Environment="STECHUHR-SERVER_CONFIG_PATH=/etc/stechuhr-server/config.toml"```
This is where the stechuhr-server default config will be created on first startup. Make sure the user `wwwrun` is allowed to write the file there`
3. Copy the stechuhr-server directory to `/srv/stechuhr-server`
4. Create the directory `/srv/stechuhr-data` and `chown wwwrun:wwwrun /srv/stechuhr-data`
5. Enable the service via `systemctl enable stechuhr-server`
6. Start the service via `systemctl start stechuhr-server`
7. Check the status via `systemctl status stechuhr-server` or display the log via `journalctl -fu stechuhr-server`
After first start the default config file should be created, have a look at it and change the defaults (e.g. with `vim /etc/stechuhr-server/config.toml`). To update the changes run `systemctl restart stechuhr-server`.
To make the server reachable from the outside a reverse proxy (like NGINX) needs to be setup. For this have a look at the example configuration file: `stechuhr.server.nginx.config` You may need to change a few things like the host or proxy_pass port.
# Configuration
......
......@@ -3,7 +3,7 @@
import re
import datetime as dt
import sqlite3
from flask import Flask, request
from flask import Flask, request, render_template
from logging.config import dictConfig
from .config import initialize_config
......@@ -15,7 +15,10 @@ APPLICATION_NAME = "stechuhr-server"
DEFAULT_CONFIG = """
[application]
ignore_get_requests = false
# Warning: setting this to true can will display all visitor movments when the
# server adress is visited. Only use for debugging!
expose_visitor_data = true
expose_current_visitor_number = true
[database]
# sqlite db path, use ":memory:" for RAM db
......@@ -145,7 +148,7 @@ def get_records(conn, cursor):
# Execute query and fill visitor with results
cursor.execute(sqlite_select_query)
records = cursor.fetchall()
visitors = [{"location":r[0], "entrance":r[1], "direction":r[2], "time":r[3], "id":r[4]} for r in records]
visitors = [{"location":r[0], "entrance":r[1], "direction":r[2], "time":dt.datetime.strptime(r[3], "%Y-%m-%d %H:%M:%S.%f"), "id":r[4]} for r in records]
except sqlite3.OperationalError as e:
# If there is no table or no records, just return an empty list
app.logger.info('Couldn\'t retrieve visitor records: {}'.format(e))
......@@ -250,22 +253,122 @@ def post():
def get():
"""
This function runs when a GET request on / is received.
Can be deactivated in the config with the ignore_get_requests setting
Can be deactivated in the config with the expose_visitor_data setting
"""
if config["application"]["ignore_get_requests"]:
if not config["application"]["expose_visitor_data"]:
app.logger.info('501, Get Request Ignored due to config settings')
return "", 501
else:
conn = sqlite3.connect(config["database"]["path"])
cursor = conn.cursor()
visitors = get_records(conn, cursor)
table = []
table.append("<table>")
table.append("<tr><th>location</th><th>entrance</th><th>direction</th><th>time</th><th>id</th></tr>")
for v in visitors:
table.append("<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>".format(v["location"], v["entrance"], v["direction"], v["time"], v["id"]))
table.append("</table>")
table = "\n".join(table)
conn.close()
app.logger.info('200, Get visitors: {}'.format(table))
return table, 200
\ No newline at end of file
app.logger.info('200, Returning {} entries from visitor list'.format(len(visitors)))
return render_template('all.html', title=APPLICATION_NAME, visitors=visitors)
# This gets run for each request
@app.route('/today', methods = ['GET'])
def today():
"""
This function runs when a GET request on /today is received.
Can be deactivated in the config with the expose_visitor_data setting
"""
if not config["application"]["expose_visitor_data"]:
app.logger.info('501, Get Request Ignored due to config settings')
return "", 501
else:
conn = sqlite3.connect(config["database"]["path"])
cursor = conn.cursor()
visitors = get_records(conn, cursor)
visitors = [v for v in visitors if v["time"].date() == dt.datetime.today().date()]
conn.close()
app.logger.info('200, Returning {} entries from visitor list (all locations)'.format(len(visitors)))
return render_template('all.html', title=APPLICATION_NAME, visitors=visitors)
# This gets run for each request
@app.route('/current', methods = ['GET'])
def current():
"""
This function runs when a GET request on /current is received.
Can be deactivated in the config with the expose_visitor_data setting
"""
if not config["application"]["expose_visitor_data"]:
app.logger.info('501, Get Request Ignored due to config settings')
return "", 501
else:
conn = sqlite3.connect(config["database"]["path"])
cursor = conn.cursor()
visitors = get_records(conn, cursor)
visitors = filter_current_visitors(visitors)
conn.close()
app.logger.info('200, Returning {} entries from visitor list (currently inside all locations)'.format(len(visitors)))
return render_template('all.html', title=APPLICATION_NAME, visitors=visitors)
# This gets run for each request
@app.route('/number', methods = ['GET'])
def number():
"""
This function runs when a GET request on /number is received.
Can be deactivated in the config with the expose_current_visitor_number setting
"""
if not config["application"]["expose_current_visitor_number"]:
app.logger.info('501, Get Request Ignored due to config settings')
return "", 501
else:
conn = sqlite3.connect(config["database"]["path"])
cursor = conn.cursor()
visitors = get_records(conn, cursor)
visitors = filter_current_visitors(visitors)
n = len(visitors)
conn.close()
app.logger.info('200, Returning number of visitors ({}, currently inside all locations)'.format(n))
return str(n), 200
def filter_current_visitors(visitors: list, hours: int=24) -> list:
"""
Return a list of visitors which are currently inside all locations.
Note: to avoid accumulating errors look only at the last n hours
"""
# Limit to last n hours
visitors = [v for v in visitors if v["time"] > dt.datetime.now() - dt.timedelta(hours=hours)]
# Get a unique list of IDs present
unique_ids = list(set([v["id"] for v in visitors]))
inside = []
# Per ID figure out if the user is inside
for iD in unique_ids:
movements = [v for v in visitors if v["id"] == iD]
incomings = [m for m in movements if m["direction"] == "in"]
outgoings = [m for m in movements if m["direction"] == "out"]
last_movement = None
if len(incomings) == 0:
# Visitor was only moving out in the last 24h, so they are not here
continue
elif not len(outgoings) == 0:
# Visitor was moving in AND out in the last 24h
last_in = incomings[-1]
last_out = outgoings[-1]
# Are they still inside the location?
if last_out["time"] < last_in["time"]:
last_movement = last_in
else:
continue
else:
# Visitor was only moving in in the last 24h
last_in = incomings[-1]
last_movement = last_in
# When the user is still here, append the last movement to the list
if last_movement is not None:
inside.append(last_movement)
return inside
\ No newline at end of file
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<table>
<tr><th></th><th>Location</th><th>Entrance</th><th>ID</th><th>Time</th></tr>
{% for visitor in visitors %}
<tr><td class="direction">{{ visitor.direction }}</td><td class="location">{{ visitor.location }}</td><td class="entrance">{{ visitor.entrance }}</td><td class="id">{{ visitor.id }}</td><td class="time">{{ visitor.time }}</td></tr>
{% endfor %}
</table>
</body>
</html>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment