first Commit

This commit is contained in:
Erik Thiele
2026-05-18 21:14:04 +02:00
commit f255e50dd3
22 changed files with 7336 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
venv/
__pycache__/
test*
*.old
.venv/
.DS_Store
.vscode

31
AGENTS.md Normal file
View File

@@ -0,0 +1,31 @@
# AGENTS.md
## Stack
- This is a single-file Flask app. The real application entrypoint is `app.py`; there is no package layout, test suite, or separate config module.
- Runtime dependency is only `Flask==3.0.3` from `requirements.txt`.
## Run
- Create the environment exactly as documented in `README.md`: `python3 -m venv .venv`, `source .venv/bin/activate`, `pip install -r requirements.txt`.
- Start the app with `python3 app.py`. `app.py` calls `app.run(debug=True)` directly under `if __name__ == "__main__"`.
- Default local URL is `http://127.0.0.1:5000`.
## Data And Side Effects
- The app writes to repo-local files next to `app.py`: SQLite database `inventory.db` and log file `inventory.log`.
- `init_db()` runs on every request via `@app.before_request`, so schema creation and the default admin bootstrap happen lazily through web traffic, not a separate init command.
- The first admin user is auto-created with username `admin` and no password; first login redirects to `/set-password`.
## Architecture Notes
- `app.py` owns routes, auth, schema management, logging, and business logic in one file. Read it before making cross-cutting changes.
- Templates live in `templates/`; static assets are in `static/`.
- Auth uses Flask session key `staff_user_id`. Access control is enforced with `login_required` and `admin_required` decorators in `app.py`.
- The transaction history stores `handled_by`; `init_db()` also contains a lightweight migration that adds this column if missing. Preserve this pattern if making schema changes against existing `inventory.db` files.
- Successful `/assign` and `/return` posts redirect to `/transactions/<id>/print`; the printable receipt flow is part of the normal workflow, not an optional extra page.
- Admin-only behavior exists in both routes and templates: only admins can reach `/admin/staff`, and only admins see recent log entries on the dashboard.
## Verification
- There are no configured tests, linters, type checks, CI workflows, or task runners in the repo.
- For changes, the practical verification step is to run `python3 app.py` and exercise the relevant route flows manually in the browser.
## Editing Cautions
- Treat `inventory.db`, `inventory.log`, and `__pycache__/` as runtime artifacts, not source files.
- `SECRET_KEY` is hardcoded to `dev-secret-key` in `app.py`; do not assume environment-based config already exists.

12
Dockerfile Normal file
View File

@@ -0,0 +1,12 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5006
CMD ["python", "app.py"]

66
README.md Normal file
View File

@@ -0,0 +1,66 @@
# Verwaltung fuer Tuerchips und Parkkarten
## Voraussetzungen
- Python 3.10 oder neuer
## Installation
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
## Starten
```bash
python3 app.py
```
Die Anwendung ist danach unter `http://127.0.0.1:5000` erreichbar.
## Funktionen
- Anmeldung fuer Bearbeiter und Admins
- Admin kann Bearbeiter und weitere Admins anlegen
- Bearbeiter vergeben ihr Passwort bei der ersten Anmeldung selbst
- Admin kann Passwort-Reset fuer Bearbeiter ausloesen
- Admin kann vorhandene Bestandsdaten per CSV importieren
- User anlegen
- Ausgabe von Tuerchips und Parkkarten
- Rueckgabe von Tuerchips und Parkkarten
- Uebersicht mit Suche und letzten Bewegungen
- Einfache Logdatei mit Datum, Medium und bearbeitendem Mitarbeiter
- Anzeige der letzten Logeintraege im Webinterface
- Bearbeiter wird auch in der Datenbankhistorie gespeichert
- Doppelte Kennungen fuer Tuerchips und Parkkarten werden bei der Ausgabe verhindert
## Datenhaltung
- Die Daten werden lokal in `inventory.db` als SQLite-Datenbank gespeichert.
- Die einfache Dateiprotokollierung wird in `inventory.log` geschrieben.
## Anmeldung
- Beim ersten Start wird automatisch ein Admin mit dem Benutzernamen `admin` angelegt.
- Dieser Admin hat zunaechst kein Passwort und wird bei der ersten Anmeldung direkt zur Passwortvergabe gefuehrt.
- Der Bearbeiter in der Historie und im Log ist immer der aktuell angemeldete Benutzer.
## Import vorhandener Daten
- Nur Admins koennen den Import ueber den Menuepunkt `Import` aufrufen.
- Der Import akzeptiert eine CSV-Datei mit Semikolon als Trennzeichen oder alternativ direkte Eingabe im Textfeld.
- Die CSV-Datei muss UTF-8 kodiert sein.
- Erwartetes Format pro Zeile:
```text
User;Typ;Kennung;Aktion
```
- Beispiel:
```text
Max Mustermann;Tuerchip;CHIP-1001;Import
Erika Muster;Parkkarte;PARK-2001;Import
```
- Eine optionale Kopfzeile `User;Typ;Kennung;Aktion` wird automatisch erkannt und uebersprungen.
- Unterstuetzte Typen sind `Tuerchip` und `Parkkarte`.
- Die Aktion `Import` uebernimmt vorhandene aktive Bestandsdaten in die Datenbank.
- Falls ein User noch nicht existiert, wird er beim Import automatisch angelegt.
- Bereits vergebene Kennungen oder widerspruechliche aktive Zuordnungen werden gesammelt als Fehler angezeigt; der Import wird erst ausgefuehrt, wenn keine Fehler mehr vorhanden sind.

781
app.py Normal file
View File

@@ -0,0 +1,781 @@
import sqlite3
from pathlib import Path
import logging
import csv
import io
from functools import wraps
from datetime import datetime
from flask import Flask, flash, g, redirect, render_template, request, session, url_for
from werkzeug.security import check_password_hash, generate_password_hash
app = Flask(__name__)
app.config["SECRET_KEY"] = "dev-secret-key"
APP_VERSION = "1.0.0"
DATABASE = Path(__file__).with_name("inventory.db")
LOGFILE = Path(__file__).with_name("inventory.log")
logging.basicConfig(
filename=LOGFILE,
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
ASSET_LABELS = {
"chip": "Tuerchip",
"parking_card": "Parkkarte",
}
ACTION_LABELS = {
"assign": "Ausgabe",
"return": "Rueckgabe",
}
PRINT_DESCRIPTIONS = {
"assign": "Empfangsbestaetigung fuer die Ausgabe eines Mediums",
"return": "Rueckgabebestaetigung fuer die Ruecknahme eines Mediums",
}
IMPORT_ACTIONS = {"import", "ausgabe", "assign"}
IMPORT_HEADER = ["user", "typ", "kennung", "aktion"]
def get_db() -> sqlite3.Connection:
if "db" not in g:
g.db = sqlite3.connect(DATABASE)
g.db.row_factory = sqlite3.Row
return g.db
@app.context_processor
def inject_app_meta() -> dict[str, str | int]:
host = request.host.split(":", 1)[0] if request.host else "-"
return {
"app_version": APP_VERSION,
"app_host": host,
"app_year": datetime.now().year,
}
@app.teardown_appcontext
def close_db(_: object | None) -> None:
db = g.pop("db", None)
if db is not None:
db.close()
def init_db() -> None:
db = get_db()
db.executescript(
"""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
full_name TEXT NOT NULL,
email TEXT,
department TEXT,
chip_code TEXT,
chip_assigned_at TEXT,
parking_card_code TEXT,
parking_card_assigned_at TEXT,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS transactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
asset_type TEXT NOT NULL CHECK (asset_type IN ('chip', 'parking_card')),
asset_code TEXT NOT NULL,
handled_by TEXT,
action TEXT NOT NULL CHECK (action IN ('assign', 'return')),
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS staff_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL,
role TEXT NOT NULL CHECK (role IN ('admin', 'staff')),
password_hash TEXT,
must_set_password INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
"""
)
columns = {
row["name"]
for row in db.execute("PRAGMA table_info(transactions)").fetchall()
}
if "handled_by" not in columns:
db.execute("ALTER TABLE transactions ADD COLUMN handled_by TEXT")
admin_exists = db.execute(
"SELECT id FROM staff_users WHERE role = 'admin' LIMIT 1"
).fetchone()
if admin_exists is None:
db.execute(
"""
INSERT INTO staff_users (username, full_name, role, password_hash, must_set_password)
VALUES (?, ?, 'admin', NULL, 1)
""",
("admin", "Administrator"),
)
db.commit()
def current_staff() -> sqlite3.Row | None:
staff_id = session.get("staff_user_id")
if not staff_id:
return None
return get_db().execute(
"SELECT id, username, full_name, role, must_set_password FROM staff_users WHERE id = ?",
(staff_id,),
).fetchone()
def login_required(view):
@wraps(view)
def wrapped_view(*args, **kwargs):
staff = current_staff()
endpoint = request.endpoint or ""
allowed_without_password = {"set_password", "logout", "static"}
if staff is None:
return redirect(url_for("login"))
if staff["must_set_password"] and endpoint not in allowed_without_password:
flash("Bitte zuerst ein Passwort setzen.")
return redirect(url_for("set_password"))
g.current_staff = staff
return view(*args, **kwargs)
return wrapped_view
def admin_required(view):
@wraps(view)
@login_required
def wrapped_view(*args, **kwargs):
if g.current_staff["role"] != "admin":
flash("Nur Admins duerfen diese Seite aufrufen.")
return redirect(url_for("index"))
return view(*args, **kwargs)
return wrapped_view
def log_asset_event(
action: str,
user_name: str,
asset_type: str,
asset_code: str,
handled_by: str,
) -> None:
logging.info(
"%s | user=%s | typ=%s | kennung=%s | bearbeiter=%s",
action,
user_name,
ASSET_LABELS[asset_type],
asset_code,
handled_by,
)
def read_recent_logs(limit: int = 20) -> list[str]:
if not LOGFILE.exists():
return []
lines = LOGFILE.read_text(encoding="utf-8").splitlines()
return list(reversed(lines[-limit:]))
def asset_code_in_use(db: sqlite3.Connection, asset_type: str, asset_code: str) -> bool:
column_name = "chip_code" if asset_type == "chip" else "parking_card_code"
existing = db.execute(
f"SELECT id FROM users WHERE {column_name} = ?",
(asset_code,),
).fetchone()
return existing is not None
def normalize_asset_type(value: str) -> str | None:
normalized = value.strip().lower()
aliases = {
"chip": "chip",
"tuerchip": "chip",
"parkkarte": "parking_card",
"parking_card": "parking_card",
"parking card": "parking_card",
}
return aliases.get(normalized)
def is_import_header(parts: list[str]) -> bool:
normalized = [part.strip().lower() for part in parts]
return normalized == IMPORT_HEADER
def get_transaction_for_print(transaction_id: int) -> sqlite3.Row | None:
return get_db().execute(
"""
SELECT t.id, t.created_at, t.asset_type, t.asset_code, t.action, t.handled_by,
u.full_name, u.email, u.department
FROM transactions t
JOIN users u ON u.id = t.user_id
WHERE t.id = ?
""",
(transaction_id,),
).fetchone()
@app.before_request
def ensure_database() -> None:
init_db()
g.current_staff = current_staff()
@app.route("/login", methods=["GET", "POST"])
def login() -> str:
if request.method == "POST":
username = request.form.get("username", "").strip()
password = request.form.get("password", "")
db = get_db()
staff = db.execute(
"SELECT * FROM staff_users WHERE username = ?",
(username,),
).fetchone()
if staff is None:
flash("Benutzer wurde nicht gefunden.")
return redirect(url_for("login"))
if staff["must_set_password"]:
session.clear()
session["staff_user_id"] = staff["id"]
flash("Bitte jetzt Ihr Passwort vergeben.")
return redirect(url_for("set_password"))
if not staff["password_hash"] or not check_password_hash(staff["password_hash"], password):
flash("Login fehlgeschlagen.")
return redirect(url_for("login"))
session.clear()
session["staff_user_id"] = staff["id"]
flash("Anmeldung erfolgreich.")
return redirect(url_for("index"))
return render_template("login.html")
@app.route("/set-password", methods=["GET", "POST"])
def set_password() -> str:
staff = current_staff()
if staff is None:
flash("Bitte zuerst anmelden.")
return redirect(url_for("login"))
if request.method == "POST":
password = request.form.get("password", "")
password_confirm = request.form.get("password_confirm", "")
if len(password) < 8:
flash("Das Passwort muss mindestens 8 Zeichen lang sein.")
return redirect(url_for("set_password"))
if password != password_confirm:
flash("Die Passwoerter stimmen nicht ueberein.")
return redirect(url_for("set_password"))
db = get_db()
db.execute(
"UPDATE staff_users SET password_hash = ?, must_set_password = 0 WHERE id = ?",
(generate_password_hash(password), staff["id"]),
)
db.commit()
flash("Passwort wurde gespeichert.")
return redirect(url_for("index"))
return render_template("set_password.html")
@app.route("/logout")
def logout() -> str:
session.clear()
flash("Sie wurden abgemeldet.")
return redirect(url_for("login"))
@app.route("/admin/staff", methods=["GET", "POST"])
@admin_required
def manage_staff() -> str:
db = get_db()
if request.method == "POST":
username = request.form.get("username", "").strip()
full_name = request.form.get("full_name", "").strip()
role = request.form.get("role", "staff").strip()
if not username or not full_name or role not in {"admin", "staff"}:
flash("Bitte alle Bearbeiterdaten korrekt eingeben.")
return redirect(url_for("manage_staff"))
try:
db.execute(
"INSERT INTO staff_users (username, full_name, role, password_hash, must_set_password) VALUES (?, ?, ?, NULL, 1)",
(username, full_name, role),
)
db.commit()
except sqlite3.IntegrityError:
flash("Der Benutzername ist bereits vergeben.")
return redirect(url_for("manage_staff"))
flash("Bearbeiter wurde angelegt. Passwort wird bei der ersten Anmeldung gesetzt.")
return redirect(url_for("manage_staff"))
staff_users = db.execute(
"SELECT id, username, full_name, role, must_set_password, created_at FROM staff_users ORDER BY full_name COLLATE NOCASE"
).fetchall()
return render_template("manage_staff.html", staff_users=staff_users)
@app.route("/admin/staff/<int:staff_id>/reset-password", methods=["POST"])
@admin_required
def reset_staff_password(staff_id: int) -> str:
db = get_db()
staff = db.execute(
"SELECT id FROM staff_users WHERE id = ?",
(staff_id,),
).fetchone()
if staff is None:
flash("Bearbeiter wurde nicht gefunden.")
return redirect(url_for("manage_staff"))
db.execute(
"UPDATE staff_users SET password_hash = NULL, must_set_password = 1 WHERE id = ?",
(staff_id,),
)
db.commit()
flash("Passwort-Reset wurde gesetzt. Der Bearbeiter muss bei der naechsten Anmeldung ein neues Passwort vergeben.")
return redirect(url_for("manage_staff"))
@app.route("/admin/staff/<int:staff_id>/delete", methods=["POST"])
@admin_required
def delete_staff(staff_id: int) -> str:
db = get_db()
staff = db.execute(
"SELECT id, role, full_name FROM staff_users WHERE id = ?",
(staff_id,),
).fetchone()
if staff is None:
flash("Bearbeiter wurde nicht gefunden.")
return redirect(url_for("manage_staff"))
if staff["id"] == g.current_staff["id"]:
flash("Der aktuell angemeldete Bearbeiter kann nicht geloescht werden.")
return redirect(url_for("manage_staff"))
if staff["role"] == "admin":
admin_count = db.execute(
"SELECT COUNT(*) AS admin_count FROM staff_users WHERE role = 'admin'"
).fetchone()
if admin_count["admin_count"] <= 1:
flash("Der letzte Admin kann nicht geloescht werden.")
return redirect(url_for("manage_staff"))
db.execute("DELETE FROM staff_users WHERE id = ?", (staff_id,))
db.commit()
flash(f"Bearbeiter '{staff['full_name']}' wurde geloescht.")
return redirect(url_for("manage_staff"))
@app.route("/admin/import", methods=["GET", "POST"])
@admin_required
def import_data() -> str:
if request.method == "POST":
upload = request.files.get("import_file")
raw_rows = request.form.get("import_rows", "")
if upload and upload.filename:
try:
decoded = upload.stream.read().decode("utf-8-sig")
except UnicodeDecodeError:
flash("Die CSV-Datei muss UTF-8 kodiert sein.", "error")
return redirect(url_for("import_data"))
csv_rows: list[str] = []
reader = csv.reader(io.StringIO(decoded), delimiter=";")
for row in reader:
if not row or not any(cell.strip() for cell in row):
continue
csv_rows.append(";".join(cell.strip() for cell in row))
raw_rows = "\n".join(csv_rows)
rows = [line.strip() for line in raw_rows.splitlines() if line.strip()]
if not rows:
flash("Bitte mindestens eine Importzeile eingeben.", "error")
return redirect(url_for("import_data"))
db = get_db()
imported_count = 0
errors: list[str] = []
operations: list[tuple[str, object]] = []
for index, row in enumerate(rows, start=1):
parts = [part.strip() for part in row.split(";")]
if is_import_header(parts):
continue
if len(parts) != 4:
errors.append(f"Zeile {index}: ungueltig. Erwartet wird: User;Typ;Kennung;Aktion")
continue
full_name, raw_asset_type, asset_code, raw_action = parts
asset_type = normalize_asset_type(raw_asset_type)
action = raw_action.strip().lower()
if not full_name or not asset_type or not asset_code:
errors.append(f"Zeile {index}: enthaelt unvollstaendige oder ungueltige Werte.")
continue
if action not in IMPORT_ACTIONS:
errors.append(f"Zeile {index}: ungueltige Aktion. Erlaubt: Import")
continue
user = db.execute(
"SELECT * FROM users WHERE full_name = ? COLLATE NOCASE",
(full_name,),
).fetchone()
pending_user = None
for operation_type, payload in operations:
if operation_type == "user" and payload["full_name"].lower() == full_name.lower():
pending_user = payload
break
current_chip_code = user["chip_code"] if user else None
current_card_code = user["parking_card_code"] if user else None
user_id = user["id"] if user else None
if pending_user is not None:
current_chip_code = pending_user["chip_code"]
current_card_code = pending_user["parking_card_code"]
if user is None and pending_user is None:
pending_user = {
"full_name": full_name,
"chip_code": None,
"parking_card_code": None,
}
operations.append(("user", pending_user))
if asset_code_in_use(db, asset_type, asset_code):
assigned_to_same_user = (
asset_type == "chip" and current_chip_code == asset_code
) or (
asset_type == "parking_card" and current_card_code == asset_code
)
if not assigned_to_same_user:
errors.append(f"Zeile {index}: Kennung '{asset_code}' ist bereits vergeben.")
continue
duplicate_in_import = False
for operation_type, payload in operations:
if operation_type != "assign":
continue
if payload["asset_type"] == asset_type and payload["asset_code"] == asset_code:
same_user = payload["full_name"].lower() == full_name.lower()
if not same_user:
duplicate_in_import = True
break
if duplicate_in_import:
errors.append(f"Zeile {index}: Kennung '{asset_code}' wird im Import mehrfach vergeben.")
continue
if asset_type == "chip":
if current_chip_code and current_chip_code != asset_code:
errors.append(f"Zeile {index}: User '{full_name}' hat bereits einen anderen Tuerchip.")
continue
if pending_user is not None:
pending_user["chip_code"] = asset_code
else:
if current_card_code and current_card_code != asset_code:
errors.append(f"Zeile {index}: User '{full_name}' hat bereits eine andere Parkkarte.")
continue
if pending_user is not None:
pending_user["parking_card_code"] = asset_code
operations.append(
(
"assign",
{
"full_name": full_name,
"user_id": user_id,
"asset_type": asset_type,
"asset_code": asset_code,
},
)
)
if errors:
flash(errors, "import-errors")
return redirect(url_for("import_data"))
user_ids_by_name: dict[str, int] = {}
for operation_type, payload in operations:
if operation_type == "user":
db.execute(
"INSERT INTO users (full_name) VALUES (?)",
(payload["full_name"],),
)
user_ids_by_name[payload["full_name"].lower()] = db.execute(
"SELECT last_insert_rowid() AS id"
).fetchone()["id"]
for operation_type, payload in operations:
if operation_type != "assign":
continue
resolved_user_id = payload["user_id"] or user_ids_by_name[payload["full_name"].lower()]
if payload["asset_type"] == "chip":
db.execute(
"UPDATE users SET chip_code = ?, chip_assigned_at = CURRENT_TIMESTAMP WHERE id = ?",
(payload["asset_code"], resolved_user_id),
)
else:
db.execute(
"UPDATE users SET parking_card_code = ?, parking_card_assigned_at = CURRENT_TIMESTAMP WHERE id = ?",
(payload["asset_code"], resolved_user_id),
)
db.execute(
"INSERT INTO transactions (user_id, asset_type, asset_code, handled_by, action) VALUES (?, ?, ?, ?, 'assign')",
(resolved_user_id, payload["asset_type"], payload["asset_code"], "Import"),
)
log_asset_event("import", payload["full_name"], payload["asset_type"], payload["asset_code"], "Import")
imported_count += 1
db.commit()
flash(f"{imported_count} Importzeilen wurden verarbeitet.", "success")
return redirect(url_for("index"))
return render_template("import_data.html")
@app.route("/")
@login_required
def index() -> str:
db = get_db()
search_query = request.args.get("q", "").strip()
params: tuple[str, ...] = ()
user_query = """
SELECT id, full_name, email, department, chip_code, chip_assigned_at,
parking_card_code, parking_card_assigned_at
FROM users
"""
if search_query:
like_value = f"%{search_query}%"
user_query += """
WHERE full_name LIKE ?
OR email LIKE ?
OR chip_code LIKE ?
OR parking_card_code LIKE ?
"""
params = (like_value, like_value, like_value, like_value)
user_query += " ORDER BY full_name COLLATE NOCASE"
users = db.execute(user_query, params).fetchall()
stats = db.execute(
"""
SELECT
COUNT(*) AS users,
SUM(CASE WHEN chip_code IS NOT NULL AND chip_code != '' THEN 1 ELSE 0 END) AS active_chips,
SUM(CASE WHEN parking_card_code IS NOT NULL AND parking_card_code != '' THEN 1 ELSE 0 END) AS active_cards
FROM users
"""
).fetchone()
transactions = db.execute(
"""
SELECT t.id, t.created_at, t.asset_type, t.asset_code, t.action, t.handled_by, u.full_name
FROM transactions t
JOIN users u ON u.id = t.user_id
ORDER BY t.created_at DESC, t.id DESC
LIMIT 10
"""
).fetchall()
recent_logs = read_recent_logs()
return render_template(
"index.html",
users=users,
transactions=transactions,
stats={
"users": stats["users"],
"active_chips": stats["active_chips"],
"active_cards": stats["active_cards"],
},
search_query=search_query,
asset_labels=ASSET_LABELS,
action_labels=ACTION_LABELS,
recent_logs=recent_logs,
)
@app.route("/users/new", methods=["GET", "POST"])
@login_required
def create_user() -> str:
if request.method == "POST":
full_name = request.form.get("full_name", "").strip()
email = request.form.get("email", "").strip() or None
department = request.form.get("department", "").strip() or None
if not full_name:
flash("Bitte einen Namen eingeben.")
return redirect(url_for("create_user"))
db = get_db()
db.execute(
"INSERT INTO users (full_name, email, department) VALUES (?, ?, ?)",
(full_name, email, department),
)
db.commit()
flash("User wurde angelegt.")
return redirect(url_for("index"))
return render_template("create_user.html")
@app.route("/assign", methods=["GET", "POST"])
@login_required
def assign_asset() -> str:
db = get_db()
if request.method == "POST":
user_id = request.form.get("user_id", "").strip()
asset_type = request.form.get("asset_type", "").strip()
asset_code = request.form.get("asset_code", "").strip()
handled_by = g.current_staff["full_name"]
if not user_id or asset_type not in {"chip", "parking_card"} or not asset_code:
flash("Bitte alle Felder fuer die Ausgabe ausfuellen.")
return redirect(url_for("assign_asset"))
user = db.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
if user is None:
flash("Ausgewaehlter User wurde nicht gefunden.")
return redirect(url_for("assign_asset"))
if asset_code_in_use(db, asset_type, asset_code):
flash("Diese Kennung ist bereits vergeben.")
return redirect(url_for("assign_asset"))
if asset_type == "chip":
if user["chip_code"]:
flash("Dieser User hat bereits einen aktiven Tuerchip.")
return redirect(url_for("assign_asset"))
db.execute(
"UPDATE users SET chip_code = ?, chip_assigned_at = CURRENT_TIMESTAMP WHERE id = ?",
(asset_code, user_id),
)
else:
if user["parking_card_code"]:
flash("Dieser User hat bereits eine aktive Parkkarte.")
return redirect(url_for("assign_asset"))
db.execute(
"UPDATE users SET parking_card_code = ?, parking_card_assigned_at = CURRENT_TIMESTAMP WHERE id = ?",
(asset_code, user_id),
)
db.execute(
"INSERT INTO transactions (user_id, asset_type, asset_code, handled_by, action) VALUES (?, ?, ?, ?, 'assign')",
(user_id, asset_type, asset_code, handled_by),
)
transaction_id = db.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
db.commit()
log_asset_event("ausgabe", user["full_name"], asset_type, asset_code, handled_by)
flash("Ausgabe wurde gespeichert.")
return redirect(url_for("print_transaction", transaction_id=transaction_id))
users = db.execute("SELECT id, full_name FROM users ORDER BY full_name COLLATE NOCASE").fetchall()
return render_template("assign_asset.html", users=users)
@app.route("/return", methods=["GET", "POST"])
@login_required
def return_asset() -> str:
db = get_db()
if request.method == "POST":
user_id = request.form.get("user_id", "").strip()
asset_type = request.form.get("asset_type", "").strip()
handled_by = g.current_staff["full_name"]
if not user_id or asset_type not in {"chip", "parking_card"}:
flash("Bitte User und Typ fuer die Rueckgabe waehlen.")
return redirect(url_for("return_asset"))
user = db.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone()
if user is None:
flash("Ausgewaehlter User wurde nicht gefunden.")
return redirect(url_for("return_asset"))
asset_code = user["chip_code"] if asset_type == "chip" else user["parking_card_code"]
if not asset_code:
flash("Fuer diesen User ist kein entsprechendes Medium aktiv hinterlegt.")
return redirect(url_for("return_asset"))
if asset_type == "chip":
db.execute(
"UPDATE users SET chip_code = NULL, chip_assigned_at = NULL WHERE id = ?",
(user_id,),
)
else:
db.execute(
"UPDATE users SET parking_card_code = NULL, parking_card_assigned_at = NULL WHERE id = ?",
(user_id,),
)
db.execute(
"INSERT INTO transactions (user_id, asset_type, asset_code, handled_by, action) VALUES (?, ?, ?, ?, 'return')",
(user_id, asset_type, asset_code, handled_by),
)
transaction_id = db.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
db.commit()
log_asset_event("rueckgabe", user["full_name"], asset_type, asset_code, handled_by)
flash("Rueckgabe wurde gespeichert.")
return redirect(url_for("print_transaction", transaction_id=transaction_id))
users = db.execute("SELECT id, full_name FROM users ORDER BY full_name COLLATE NOCASE").fetchall()
return render_template("return_asset.html", users=users)
@app.route("/transactions/<int:transaction_id>/print")
@login_required
def print_transaction(transaction_id: int) -> str:
transaction = get_transaction_for_print(transaction_id)
if transaction is None:
flash("Druckbeleg wurde nicht gefunden.")
return redirect(url_for("index"))
return render_template(
"print_transaction.html",
transaction=transaction,
asset_labels=ASSET_LABELS,
action_labels=ACTION_LABELS,
print_descriptions=PRINT_DESCRIPTIONS,
)
if __name__ == "__main__":
app.run(debug=True)

13
docker-compose.yml Normal file
View File

@@ -0,0 +1,13 @@
services:
keyVerwaltung:
container_name: keyVerwaltung
build:
context: .
dockerfile: Dockerfile
platforms:
- linux/amd64
image: gitea.teamthiele.de/ethiele/keyVerwaltung:latest
ports:
- "5006:5006"
restart: unless-stopped

BIN
inventory.db Normal file

Binary file not shown.

709
inventory.log Normal file
View File

@@ -0,0 +1,709 @@
2026-05-18 14:23:42,463 WARNING * Debugger is active!
2026-05-18 14:23:42,491 INFO * Debugger PIN: 428-899-358
2026-05-18 14:25:32,695 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 14:27:46,631 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
2026-05-18 14:27:46,631 INFO Press CTRL+C to quit
2026-05-18 14:27:46,632 INFO * Restarting with stat
2026-05-18 14:27:46,834 WARNING * Debugger is active!
2026-05-18 14:27:46,851 INFO * Debugger PIN: 428-899-358
2026-05-18 14:28:03,048 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
2026-05-18 14:28:03,048 INFO Press CTRL+C to quit
2026-05-18 14:28:03,049 INFO * Restarting with stat
2026-05-18 14:28:03,246 WARNING * Debugger is active!
2026-05-18 14:28:03,261 INFO * Debugger PIN: 428-899-358
2026-05-18 14:28:27,475 INFO 127.0.0.1 - - [18/May/2026 14:28:27] "GET / HTTP/1.1" 200 -
2026-05-18 14:28:59,158 INFO 127.0.0.1 - - [18/May/2026 14:28:59] "GET / HTTP/1.1" 200 -
2026-05-18 14:29:00,812 INFO 127.0.0.1 - - [18/May/2026 14:29:00] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:29:16,461 INFO 127.0.0.1 - - [18/May/2026 14:29:16] "POST /users/new HTTP/1.1" 302 -
2026-05-18 14:29:16,478 INFO 127.0.0.1 - - [18/May/2026 14:29:16] "GET / HTTP/1.1" 200 -
2026-05-18 14:29:24,608 INFO 127.0.0.1 - - [18/May/2026 14:29:24] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:29:56,152 INFO ausgabe | user=Erik Thiele | typ=Tuerchip | kennung=120 | bearbeiter=Erik Thiele
2026-05-18 14:29:56,153 INFO 127.0.0.1 - - [18/May/2026 14:29:56] "POST /assign HTTP/1.1" 302 -
2026-05-18 14:29:56,172 INFO 127.0.0.1 - - [18/May/2026 14:29:56] "GET / HTTP/1.1" 200 -
2026-05-18 14:30:07,243 INFO 127.0.0.1 - - [18/May/2026 14:30:07] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:30:24,830 INFO ausgabe | user=Erik Thiele | typ=Parkkarte | kennung=1234567 | bearbeiter=Erik Thiele
2026-05-18 14:30:24,831 INFO 127.0.0.1 - - [18/May/2026 14:30:24] "POST /assign HTTP/1.1" 302 -
2026-05-18 14:30:24,857 INFO 127.0.0.1 - - [18/May/2026 14:30:24] "GET / HTTP/1.1" 200 -
2026-05-18 14:31:04,359 INFO 127.0.0.1 - - [18/May/2026 14:31:04] "GET /?q=erik HTTP/1.1" 200 -
2026-05-18 14:31:17,431 INFO 127.0.0.1 - - [18/May/2026 14:31:17] "GET /?q=erik HTTP/1.1" 200 -
2026-05-18 14:31:27,322 INFO 127.0.0.1 - - [18/May/2026 14:31:27] "GET /?q=120 HTTP/1.1" 200 -
2026-05-18 14:31:30,855 INFO 127.0.0.1 - - [18/May/2026 14:31:30] "GET /?q=120 HTTP/1.1" 200 -
2026-05-18 14:31:35,341 INFO 127.0.0.1 - - [18/May/2026 14:31:35] "GET / HTTP/1.1" 200 -
2026-05-18 14:36:58,604 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 14:36:58,639 INFO * Restarting with stat
2026-05-18 14:36:59,593 WARNING * Debugger is active!
2026-05-18 14:36:59,623 INFO * Debugger PIN: 428-899-358
2026-05-18 14:38:46,585 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 14:38:46,618 INFO * Restarting with stat
2026-05-18 14:38:46,951 WARNING * Debugger is active!
2026-05-18 14:38:46,971 INFO * Debugger PIN: 428-899-358
2026-05-18 14:39:43,527 INFO 127.0.0.1 - - [18/May/2026 14:39:43] "GET / HTTP/1.1" 302 -
2026-05-18 14:39:43,551 INFO 127.0.0.1 - - [18/May/2026 14:39:43] "GET /login HTTP/1.1" 200 -
2026-05-18 14:39:52,116 INFO 127.0.0.1 - - [18/May/2026 14:39:52] "POST /login HTTP/1.1" 302 -
2026-05-18 14:39:52,122 INFO 127.0.0.1 - - [18/May/2026 14:39:52] "GET /set-password HTTP/1.1" 200 -
2026-05-18 14:40:04,983 INFO 127.0.0.1 - - [18/May/2026 14:40:04] "POST /set-password HTTP/1.1" 302 -
2026-05-18 14:40:05,015 INFO 127.0.0.1 - - [18/May/2026 14:40:05] "GET / HTTP/1.1" 200 -
2026-05-18 14:40:20,652 INFO 127.0.0.1 - - [18/May/2026 14:40:20] "GET / HTTP/1.1" 200 -
2026-05-18 14:40:21,757 INFO 127.0.0.1 - - [18/May/2026 14:40:21] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:40:24,177 INFO 127.0.0.1 - - [18/May/2026 14:40:24] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:40:25,515 INFO 127.0.0.1 - - [18/May/2026 14:40:25] "GET /return HTTP/1.1" 200 -
2026-05-18 14:40:26,507 INFO 127.0.0.1 - - [18/May/2026 14:40:26] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 14:43:46,955 INFO 127.0.0.1 - - [18/May/2026 14:43:46] "GET / HTTP/1.1" 200 -
2026-05-18 14:45:26,947 INFO 127.0.0.1 - - [18/May/2026 14:45:26] "GET / HTTP/1.1" 200 -
2026-05-18 14:51:22,094 INFO 127.0.0.1 - - [18/May/2026 14:51:22] "GET / HTTP/1.1" 200 -
2026-05-18 14:51:22,134 INFO 127.0.0.1 - - [18/May/2026 14:51:22] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 14:51:22,139 INFO 127.0.0.1 - - [18/May/2026 14:51:22] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 14:51:27,874 INFO 127.0.0.1 - - [18/May/2026 14:51:27] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:51:27,897 INFO 127.0.0.1 - - [18/May/2026 14:51:27] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:29,057 INFO 127.0.0.1 - - [18/May/2026 14:51:29] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:51:29,080 INFO 127.0.0.1 - - [18/May/2026 14:51:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:29,947 INFO 127.0.0.1 - - [18/May/2026 14:51:29] "GET /return HTTP/1.1" 200 -
2026-05-18 14:51:29,979 INFO 127.0.0.1 - - [18/May/2026 14:51:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:30,825 INFO 127.0.0.1 - - [18/May/2026 14:51:30] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 14:51:30,857 INFO 127.0.0.1 - - [18/May/2026 14:51:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:53,088 INFO 127.0.0.1 - - [18/May/2026 14:51:53] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:51:53,121 INFO 127.0.0.1 - - [18/May/2026 14:51:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:55,405 INFO 127.0.0.1 - - [18/May/2026 14:51:55] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:51:55,431 INFO 127.0.0.1 - - [18/May/2026 14:51:55] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:58,704 INFO 127.0.0.1 - - [18/May/2026 14:51:58] "GET /return HTTP/1.1" 200 -
2026-05-18 14:51:58,726 INFO 127.0.0.1 - - [18/May/2026 14:51:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:51:59,901 INFO 127.0.0.1 - - [18/May/2026 14:51:59] "GET /return HTTP/1.1" 200 -
2026-05-18 14:51:59,934 INFO 127.0.0.1 - - [18/May/2026 14:51:59] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:52:01,807 INFO 127.0.0.1 - - [18/May/2026 14:52:01] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 14:52:01,828 INFO 127.0.0.1 - - [18/May/2026 14:52:01] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:52:09,060 INFO 127.0.0.1 - - [18/May/2026 14:52:09] "GET / HTTP/1.1" 200 -
2026-05-18 14:52:09,091 INFO 127.0.0.1 - - [18/May/2026 14:52:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:52:18,275 INFO 127.0.0.1 - - [18/May/2026 14:52:18] "GET /logout HTTP/1.1" 302 -
2026-05-18 14:52:18,286 INFO 127.0.0.1 - - [18/May/2026 14:52:18] "GET /login HTTP/1.1" 200 -
2026-05-18 14:52:18,306 INFO 127.0.0.1 - - [18/May/2026 14:52:18] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:52:18,343 INFO 127.0.0.1 - - [18/May/2026 14:52:18] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 14:52:24,523 INFO 127.0.0.1 - - [18/May/2026 14:52:24] "GET /login HTTP/1.1" 200 -
2026-05-18 14:52:24,543 INFO 127.0.0.1 - - [18/May/2026 14:52:24] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:53:35,342 INFO 127.0.0.1 - - [18/May/2026 14:53:35] "GET /login HTTP/1.1" 200 -
2026-05-18 14:53:35,372 INFO 127.0.0.1 - - [18/May/2026 14:53:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:54:51,498 INFO 127.0.0.1 - - [18/May/2026 14:54:51] "GET /login HTTP/1.1" 200 -
2026-05-18 14:54:51,521 INFO 127.0.0.1 - - [18/May/2026 14:54:51] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:55:01,447 INFO 127.0.0.1 - - [18/May/2026 14:55:01] "GET /login HTTP/1.1" 200 -
2026-05-18 14:55:01,472 INFO 127.0.0.1 - - [18/May/2026 14:55:01] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:55:05,929 INFO 127.0.0.1 - - [18/May/2026 14:55:05] "POST /login HTTP/1.1" 302 -
2026-05-18 14:55:05,955 INFO 127.0.0.1 - - [18/May/2026 14:55:05] "GET / HTTP/1.1" 200 -
2026-05-18 14:55:06,030 INFO 127.0.0.1 - - [18/May/2026 14:55:06] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:55:06,039 INFO 127.0.0.1 - - [18/May/2026 14:55:06] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 14:56:04,001 INFO 127.0.0.1 - - [18/May/2026 14:56:04] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:56:04,038 INFO 127.0.0.1 - - [18/May/2026 14:56:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:56:05,926 INFO 127.0.0.1 - - [18/May/2026 14:56:05] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:56:05,948 INFO 127.0.0.1 - - [18/May/2026 14:56:05] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:56:07,459 INFO 127.0.0.1 - - [18/May/2026 14:56:07] "GET /return HTTP/1.1" 200 -
2026-05-18 14:56:07,482 INFO 127.0.0.1 - - [18/May/2026 14:56:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:56:09,495 INFO 127.0.0.1 - - [18/May/2026 14:56:09] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 14:56:09,528 INFO 127.0.0.1 - - [18/May/2026 14:56:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:56:12,825 INFO 127.0.0.1 - - [18/May/2026 14:56:12] "GET / HTTP/1.1" 200 -
2026-05-18 14:56:12,846 INFO 127.0.0.1 - - [18/May/2026 14:56:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:11,098 INFO 127.0.0.1 - - [18/May/2026 14:58:11] "GET / HTTP/1.1" 200 -
2026-05-18 14:58:11,121 INFO 127.0.0.1 - - [18/May/2026 14:58:11] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:12,620 INFO 127.0.0.1 - - [18/May/2026 14:58:12] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:58:12,644 INFO 127.0.0.1 - - [18/May/2026 14:58:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:15,051 INFO 127.0.0.1 - - [18/May/2026 14:58:15] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:58:15,070 INFO 127.0.0.1 - - [18/May/2026 14:58:15] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:16,525 INFO 127.0.0.1 - - [18/May/2026 14:58:16] "GET /assign HTTP/1.1" 200 -
2026-05-18 14:58:16,557 INFO 127.0.0.1 - - [18/May/2026 14:58:16] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:17,670 INFO 127.0.0.1 - - [18/May/2026 14:58:17] "GET /return HTTP/1.1" 200 -
2026-05-18 14:58:17,694 INFO 127.0.0.1 - - [18/May/2026 14:58:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:18,850 INFO 127.0.0.1 - - [18/May/2026 14:58:18] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 14:58:18,872 INFO 127.0.0.1 - - [18/May/2026 14:58:18] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:21,180 INFO 127.0.0.1 - - [18/May/2026 14:58:21] "GET / HTTP/1.1" 200 -
2026-05-18 14:58:21,205 INFO 127.0.0.1 - - [18/May/2026 14:58:21] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:24,907 INFO 127.0.0.1 - - [18/May/2026 14:58:24] "GET / HTTP/1.1" 200 -
2026-05-18 14:58:25,076 INFO 127.0.0.1 - - [18/May/2026 14:58:25] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 14:58:25,082 INFO 127.0.0.1 - - [18/May/2026 14:58:25] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 14:58:26,874 INFO 127.0.0.1 - - [18/May/2026 14:58:26] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:58:26,894 INFO 127.0.0.1 - - [18/May/2026 14:58:26] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:58:28,238 INFO 127.0.0.1 - - [18/May/2026 14:58:28] "GET /users/new HTTP/1.1" 200 -
2026-05-18 14:58:28,368 INFO 127.0.0.1 - - [18/May/2026 14:58:28] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 14:58:28,372 INFO 127.0.0.1 - - [18/May/2026 14:58:28] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 14:58:30,860 INFO 127.0.0.1 - - [18/May/2026 14:58:30] "GET / HTTP/1.1" 200 -
2026-05-18 14:58:30,879 INFO 127.0.0.1 - - [18/May/2026 14:58:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 14:59:55,091 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
2026-05-18 14:59:55,092 INFO Press CTRL+C to quit
2026-05-18 14:59:55,095 INFO * Restarting with stat
2026-05-18 14:59:55,306 WARNING * Debugger is active!
2026-05-18 14:59:55,325 INFO * Debugger PIN: 428-899-358
2026-05-18 15:00:03,755 INFO 127.0.0.1 - - [18/May/2026 15:00:03] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:03,781 INFO 127.0.0.1 - - [18/May/2026 15:00:03] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:06,303 INFO 127.0.0.1 - - [18/May/2026 15:00:06] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:00:06,326 INFO 127.0.0.1 - - [18/May/2026 15:00:06] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:08,982 INFO 127.0.0.1 - - [18/May/2026 15:00:08] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:00:09,001 INFO 127.0.0.1 - - [18/May/2026 15:00:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:10,975 INFO 127.0.0.1 - - [18/May/2026 15:00:10] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:00:11,004 INFO 127.0.0.1 - - [18/May/2026 15:00:11] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:12,451 INFO 127.0.0.1 - - [18/May/2026 15:00:12] "GET /return HTTP/1.1" 200 -
2026-05-18 15:00:12,473 INFO 127.0.0.1 - - [18/May/2026 15:00:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:13,662 INFO 127.0.0.1 - - [18/May/2026 15:00:13] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:00:13,689 INFO 127.0.0.1 - - [18/May/2026 15:00:13] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:15,242 INFO 127.0.0.1 - - [18/May/2026 15:00:15] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:15,275 INFO 127.0.0.1 - - [18/May/2026 15:00:15] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:17,438 INFO 127.0.0.1 - - [18/May/2026 15:00:17] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:17,458 INFO 127.0.0.1 - - [18/May/2026 15:00:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:22,487 INFO 127.0.0.1 - - [18/May/2026 15:00:22] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:00:22,522 INFO 127.0.0.1 - - [18/May/2026 15:00:22] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:28,656 INFO 127.0.0.1 - - [18/May/2026 15:00:28] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:00:28,787 INFO 127.0.0.1 - - [18/May/2026 15:00:28] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:00:28,791 INFO 127.0.0.1 - - [18/May/2026 15:00:28] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:00:30,396 INFO 127.0.0.1 - - [18/May/2026 15:00:30] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:30,422 INFO 127.0.0.1 - - [18/May/2026 15:00:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:33,657 INFO 127.0.0.1 - - [18/May/2026 15:00:33] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:33,677 INFO 127.0.0.1 - - [18/May/2026 15:00:33] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:35,094 INFO 127.0.0.1 - - [18/May/2026 15:00:35] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:35,111 INFO 127.0.0.1 - - [18/May/2026 15:00:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:51,171 INFO 127.0.0.1 - - [18/May/2026 15:00:51] "GET / HTTP/1.1" 302 -
2026-05-18 15:00:51,175 INFO 127.0.0.1 - - [18/May/2026 15:00:51] "GET /login HTTP/1.1" 200 -
2026-05-18 15:00:51,312 INFO 127.0.0.1 - - [18/May/2026 15:00:51] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:00:51,316 INFO 127.0.0.1 - - [18/May/2026 15:00:51] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:00:58,718 INFO 127.0.0.1 - - [18/May/2026 15:00:58] "POST /login HTTP/1.1" 302 -
2026-05-18 15:00:58,748 INFO 127.0.0.1 - - [18/May/2026 15:00:58] "GET / HTTP/1.1" 200 -
2026-05-18 15:00:58,779 INFO 127.0.0.1 - - [18/May/2026 15:00:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:00:58,784 INFO 127.0.0.1 - - [18/May/2026 15:00:58] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:01:01,021 INFO 127.0.0.1 - - [18/May/2026 15:01:01] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:01:01,043 INFO 127.0.0.1 - - [18/May/2026 15:01:01] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:01:02,178 INFO 127.0.0.1 - - [18/May/2026 15:01:02] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:01:02,195 INFO 127.0.0.1 - - [18/May/2026 15:01:02] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:18,767 INFO 127.0.0.1 - - [18/May/2026 15:02:18] "GET / HTTP/1.1" 200 -
2026-05-18 15:02:18,790 INFO 127.0.0.1 - - [18/May/2026 15:02:18] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:20,657 INFO 127.0.0.1 - - [18/May/2026 15:02:20] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:02:20,680 INFO 127.0.0.1 - - [18/May/2026 15:02:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:24,698 INFO 127.0.0.1 - - [18/May/2026 15:02:24] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:02:24,853 INFO 127.0.0.1 - - [18/May/2026 15:02:24] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:02:24,857 INFO 127.0.0.1 - - [18/May/2026 15:02:24] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:02:28,193 INFO 127.0.0.1 - - [18/May/2026 15:02:28] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:02:28,224 INFO 127.0.0.1 - - [18/May/2026 15:02:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:29,573 INFO 127.0.0.1 - - [18/May/2026 15:02:29] "GET /return HTTP/1.1" 200 -
2026-05-18 15:02:29,594 INFO 127.0.0.1 - - [18/May/2026 15:02:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:30,264 INFO 127.0.0.1 - - [18/May/2026 15:02:30] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:02:30,291 INFO 127.0.0.1 - - [18/May/2026 15:02:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:32,218 INFO 127.0.0.1 - - [18/May/2026 15:02:32] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:02:32,238 INFO 127.0.0.1 - - [18/May/2026 15:02:32] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:33,403 INFO 127.0.0.1 - - [18/May/2026 15:02:33] "GET / HTTP/1.1" 200 -
2026-05-18 15:02:33,426 INFO 127.0.0.1 - - [18/May/2026 15:02:33] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:02:34,928 INFO 127.0.0.1 - - [18/May/2026 15:02:34] "GET / HTTP/1.1" 200 -
2026-05-18 15:02:34,945 INFO 127.0.0.1 - - [18/May/2026 15:02:34] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:03:52,750 INFO 127.0.0.1 - - [18/May/2026 15:03:52] "GET / HTTP/1.1" 200 -
2026-05-18 15:03:52,772 INFO 127.0.0.1 - - [18/May/2026 15:03:52] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:03:57,589 INFO 127.0.0.1 - - [18/May/2026 15:03:57] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:03:57,612 INFO 127.0.0.1 - - [18/May/2026 15:03:57] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:03:58,815 INFO 127.0.0.1 - - [18/May/2026 15:03:58] "GET / HTTP/1.1" 200 -
2026-05-18 15:03:58,846 INFO 127.0.0.1 - - [18/May/2026 15:03:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:29,725 INFO 127.0.0.1 - - [18/May/2026 15:04:29] "GET / HTTP/1.1" 200 -
2026-05-18 15:04:29,745 INFO 127.0.0.1 - - [18/May/2026 15:04:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:37,230 INFO 127.0.0.1 - - [18/May/2026 15:04:37] "GET / HTTP/1.1" 200 -
2026-05-18 15:04:37,254 INFO 127.0.0.1 - - [18/May/2026 15:04:37] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:38,585 INFO 127.0.0.1 - - [18/May/2026 15:04:38] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:04:38,610 INFO 127.0.0.1 - - [18/May/2026 15:04:38] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:44,172 INFO 127.0.0.1 - - [18/May/2026 15:04:44] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:04:44,193 INFO 127.0.0.1 - - [18/May/2026 15:04:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:45,202 INFO 127.0.0.1 - - [18/May/2026 15:04:45] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:04:45,225 INFO 127.0.0.1 - - [18/May/2026 15:04:45] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:53,743 INFO 127.0.0.1 - - [18/May/2026 15:04:53] "GET /logout HTTP/1.1" 302 -
2026-05-18 15:04:53,753 INFO 127.0.0.1 - - [18/May/2026 15:04:53] "GET /login HTTP/1.1" 200 -
2026-05-18 15:04:53,776 INFO 127.0.0.1 - - [18/May/2026 15:04:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:53,805 INFO 127.0.0.1 - - [18/May/2026 15:04:53] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:04:57,201 INFO 127.0.0.1 - - [18/May/2026 15:04:57] "POST /login HTTP/1.1" 302 -
2026-05-18 15:04:57,231 INFO 127.0.0.1 - - [18/May/2026 15:04:57] "GET / HTTP/1.1" 200 -
2026-05-18 15:04:57,255 INFO 127.0.0.1 - - [18/May/2026 15:04:57] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:04:57,262 INFO 127.0.0.1 - - [18/May/2026 15:04:57] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:05:00,054 INFO 127.0.0.1 - - [18/May/2026 15:05:00] "GET /logout HTTP/1.1" 302 -
2026-05-18 15:05:00,063 INFO 127.0.0.1 - - [18/May/2026 15:05:00] "GET /login HTTP/1.1" 200 -
2026-05-18 15:05:00,083 INFO 127.0.0.1 - - [18/May/2026 15:05:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:05:00,106 INFO 127.0.0.1 - - [18/May/2026 15:05:00] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:05:05,077 INFO 127.0.0.1 - - [18/May/2026 15:05:05] "POST /login HTTP/1.1" 302 -
2026-05-18 15:05:05,109 INFO 127.0.0.1 - - [18/May/2026 15:05:05] "GET / HTTP/1.1" 200 -
2026-05-18 15:05:05,138 INFO 127.0.0.1 - - [18/May/2026 15:05:05] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:05:05,143 INFO 127.0.0.1 - - [18/May/2026 15:05:05] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:05:13,575 INFO 127.0.0.1 - - [18/May/2026 15:05:13] "GET /?q= HTTP/1.1" 200 -
2026-05-18 15:05:13,598 INFO 127.0.0.1 - - [18/May/2026 15:05:13] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:05:33,511 INFO 127.0.0.1 - - [18/May/2026 15:05:33] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:05:33,548 INFO 127.0.0.1 - - [18/May/2026 15:05:33] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:05:38,642 INFO 127.0.0.1 - - [18/May/2026 15:05:38] "GET / HTTP/1.1" 200 -
2026-05-18 15:05:38,680 INFO 127.0.0.1 - - [18/May/2026 15:05:38] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:06:39,481 INFO 127.0.0.1 - - [18/May/2026 15:06:39] "GET / HTTP/1.1" 200 -
2026-05-18 15:06:39,593 INFO 127.0.0.1 - - [18/May/2026 15:06:39] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:07:42,446 INFO 127.0.0.1 - - [18/May/2026 15:07:42] "GET / HTTP/1.1" 200 -
2026-05-18 15:07:42,472 INFO 127.0.0.1 - - [18/May/2026 15:07:42] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:08:04,884 INFO 127.0.0.1 - - [18/May/2026 15:08:04] "GET /return HTTP/1.1" 200 -
2026-05-18 15:08:04,914 INFO 127.0.0.1 - - [18/May/2026 15:08:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:08:16,423 INFO rueckgabe | user=Erik Thiele | typ=Parkkarte | kennung=1234567 | bearbeiter=Administrator
2026-05-18 15:08:16,424 INFO 127.0.0.1 - - [18/May/2026 15:08:16] "POST /return HTTP/1.1" 302 -
2026-05-18 15:08:16,454 INFO 127.0.0.1 - - [18/May/2026 15:08:16] "GET / HTTP/1.1" 200 -
2026-05-18 15:08:16,478 INFO 127.0.0.1 - - [18/May/2026 15:08:16] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:08:16,491 INFO 127.0.0.1 - - [18/May/2026 15:08:16] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:08:33,800 INFO 127.0.0.1 - - [18/May/2026 15:08:33] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:08:33,832 INFO 127.0.0.1 - - [18/May/2026 15:08:33] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:08:42,613 INFO ausgabe | user=Erik Thiele | typ=Parkkarte | kennung=122223333 | bearbeiter=Administrator
2026-05-18 15:08:42,614 INFO 127.0.0.1 - - [18/May/2026 15:08:42] "POST /assign HTTP/1.1" 302 -
2026-05-18 15:08:42,647 INFO 127.0.0.1 - - [18/May/2026 15:08:42] "GET / HTTP/1.1" 200 -
2026-05-18 15:08:42,678 INFO 127.0.0.1 - - [18/May/2026 15:08:42] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:08:42,684 INFO 127.0.0.1 - - [18/May/2026 15:08:42] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:10:40,947 INFO 127.0.0.1 - - [18/May/2026 15:10:40] "GET / HTTP/1.1" 200 -
2026-05-18 15:10:40,970 INFO 127.0.0.1 - - [18/May/2026 15:10:40] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:12:29,420 INFO 127.0.0.1 - - [18/May/2026 15:12:29] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:29,443 INFO 127.0.0.1 - - [18/May/2026 15:12:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:12:42,198 INFO 127.0.0.1 - - [18/May/2026 15:12:42] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:42,365 INFO 127.0.0.1 - - [18/May/2026 15:12:42] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:42,371 INFO 127.0.0.1 - - [18/May/2026 15:12:42] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:12:43,719 INFO 127.0.0.1 - - [18/May/2026 15:12:43] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:43,843 INFO 127.0.0.1 - - [18/May/2026 15:12:43] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:43,846 INFO 127.0.0.1 - - [18/May/2026 15:12:43] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:12:44,053 INFO 127.0.0.1 - - [18/May/2026 15:12:44] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:44,206 INFO 127.0.0.1 - - [18/May/2026 15:12:44] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:45,479 INFO 127.0.0.1 - - [18/May/2026 15:12:45] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:45,606 INFO 127.0.0.1 - - [18/May/2026 15:12:45] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:45,610 INFO 127.0.0.1 - - [18/May/2026 15:12:45] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:12:45,817 INFO 127.0.0.1 - - [18/May/2026 15:12:45] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:45,971 INFO 127.0.0.1 - - [18/May/2026 15:12:45] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:47,080 INFO 127.0.0.1 - - [18/May/2026 15:12:47] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:47,268 INFO 127.0.0.1 - - [18/May/2026 15:12:47] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 15:12:47,278 INFO 127.0.0.1 - - [18/May/2026 15:12:47] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 15:12:48,638 INFO 127.0.0.1 - - [18/May/2026 15:12:48] "GET / HTTP/1.1" 200 -
2026-05-18 15:12:48,656 INFO 127.0.0.1 - - [18/May/2026 15:12:48] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:13:04,246 INFO 127.0.0.1 - - [18/May/2026 15:13:04] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:13:04,272 INFO 127.0.0.1 - - [18/May/2026 15:13:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:13:12,342 INFO 127.0.0.1 - - [18/May/2026 15:13:12] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:13:12,370 INFO 127.0.0.1 - - [18/May/2026 15:13:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:13:18,031 INFO 127.0.0.1 - - [18/May/2026 15:13:18] "GET /return HTTP/1.1" 200 -
2026-05-18 15:13:18,054 INFO 127.0.0.1 - - [18/May/2026 15:13:18] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:13:20,777 INFO 127.0.0.1 - - [18/May/2026 15:13:20] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:13:20,801 INFO 127.0.0.1 - - [18/May/2026 15:13:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:13:24,403 INFO 127.0.0.1 - - [18/May/2026 15:13:24] "GET / HTTP/1.1" 200 -
2026-05-18 15:13:24,431 INFO 127.0.0.1 - - [18/May/2026 15:13:24] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:14:42,665 INFO 127.0.0.1 - - [18/May/2026 15:14:42] "GET / HTTP/1.1" 200 -
2026-05-18 15:14:42,687 INFO 127.0.0.1 - - [18/May/2026 15:14:42] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:16:21,627 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 15:16:21,670 INFO * Restarting with stat
2026-05-18 15:16:22,024 WARNING * Debugger is active!
2026-05-18 15:16:22,044 INFO * Debugger PIN: 428-899-358
2026-05-18 15:16:32,774 INFO 127.0.0.1 - - [18/May/2026 15:16:32] "GET / HTTP/1.1" 200 -
2026-05-18 15:16:32,800 INFO 127.0.0.1 - - [18/May/2026 15:16:32] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:18:03,982 INFO 127.0.0.1 - - [18/May/2026 15:18:03] "GET / HTTP/1.1" 200 -
2026-05-18 15:18:04,004 INFO 127.0.0.1 - - [18/May/2026 15:18:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:18:10,610 INFO 127.0.0.1 - - [18/May/2026 15:18:10] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:18:10,641 INFO 127.0.0.1 - - [18/May/2026 15:18:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:18:27,254 INFO 127.0.0.1 - - [18/May/2026 15:18:27] "POST /admin/staff HTTP/1.1" 302 -
2026-05-18 15:18:27,263 INFO 127.0.0.1 - - [18/May/2026 15:18:27] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:18:27,286 INFO 127.0.0.1 - - [18/May/2026 15:18:27] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:18:27,302 INFO 127.0.0.1 - - [18/May/2026 15:18:27] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 15:18:44,742 INFO 127.0.0.1 - - [18/May/2026 15:18:44] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:18:44,764 INFO 127.0.0.1 - - [18/May/2026 15:18:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:19:58,673 INFO 127.0.0.1 - - [18/May/2026 15:19:58] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:19:58,695 INFO 127.0.0.1 - - [18/May/2026 15:19:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:20:00,485 INFO 127.0.0.1 - - [18/May/2026 15:20:00] "GET / HTTP/1.1" 200 -
2026-05-18 15:20:00,507 INFO 127.0.0.1 - - [18/May/2026 15:20:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:20:05,565 INFO 127.0.0.1 - - [18/May/2026 15:20:05] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:20:05,589 INFO 127.0.0.1 - - [18/May/2026 15:20:05] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:21:52,561 INFO 127.0.0.1 - - [18/May/2026 15:21:52] "GET / HTTP/1.1" 200 -
2026-05-18 15:21:52,588 INFO 127.0.0.1 - - [18/May/2026 15:21:52] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:22:04,902 INFO 127.0.0.1 - - [18/May/2026 15:22:04] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:22:04,930 INFO 127.0.0.1 - - [18/May/2026 15:22:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:22:06,242 INFO 127.0.0.1 - - [18/May/2026 15:22:06] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:22:06,275 INFO 127.0.0.1 - - [18/May/2026 15:22:06] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:22:07,739 INFO 127.0.0.1 - - [18/May/2026 15:22:07] "GET /return HTTP/1.1" 200 -
2026-05-18 15:22:07,760 INFO 127.0.0.1 - - [18/May/2026 15:22:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:22:08,924 INFO 127.0.0.1 - - [18/May/2026 15:22:08] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:22:08,955 INFO 127.0.0.1 - - [18/May/2026 15:22:08] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:22:23,933 INFO 127.0.0.1 - - [18/May/2026 15:22:23] "GET / HTTP/1.1" 200 -
2026-05-18 15:22:23,958 INFO 127.0.0.1 - - [18/May/2026 15:22:23] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:23:34,772 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 15:23:34,815 INFO * Restarting with stat
2026-05-18 15:23:35,156 WARNING * Debugger is active!
2026-05-18 15:23:35,176 INFO * Debugger PIN: 428-899-358
2026-05-18 15:23:53,582 INFO 127.0.0.1 - - [18/May/2026 15:23:53] "GET / HTTP/1.1" 200 -
2026-05-18 15:23:53,608 INFO 127.0.0.1 - - [18/May/2026 15:23:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:24:00,814 INFO 127.0.0.1 - - [18/May/2026 15:24:00] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:24:00,840 INFO 127.0.0.1 - - [18/May/2026 15:24:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:25:09,784 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 15:25:09,817 INFO * Restarting with stat
2026-05-18 15:25:10,185 WARNING * Debugger is active!
2026-05-18 15:25:10,203 INFO * Debugger PIN: 428-899-358
2026-05-18 15:27:35,140 INFO 127.0.0.1 - - [18/May/2026 15:27:35] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:27:35,167 INFO 127.0.0.1 - - [18/May/2026 15:27:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:57:58,313 INFO 127.0.0.1 - - [18/May/2026 15:57:58] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:57:58,364 INFO 127.0.0.1 - - [18/May/2026 15:57:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:57:59,454 INFO 127.0.0.1 - - [18/May/2026 15:57:59] "GET / HTTP/1.1" 200 -
2026-05-18 15:57:59,484 INFO 127.0.0.1 - - [18/May/2026 15:57:59] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:58:02,881 INFO 127.0.0.1 - - [18/May/2026 15:58:02] "GET /users/new HTTP/1.1" 200 -
2026-05-18 15:58:02,915 INFO 127.0.0.1 - - [18/May/2026 15:58:02] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:58:04,180 INFO 127.0.0.1 - - [18/May/2026 15:58:04] "GET /assign HTTP/1.1" 200 -
2026-05-18 15:58:04,207 INFO 127.0.0.1 - - [18/May/2026 15:58:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:58:05,201 INFO 127.0.0.1 - - [18/May/2026 15:58:05] "GET /return HTTP/1.1" 200 -
2026-05-18 15:58:05,227 INFO 127.0.0.1 - - [18/May/2026 15:58:05] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:58:06,248 INFO 127.0.0.1 - - [18/May/2026 15:58:06] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 15:58:06,280 INFO 127.0.0.1 - - [18/May/2026 15:58:06] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 15:58:07,472 INFO 127.0.0.1 - - [18/May/2026 15:58:07] "GET / HTTP/1.1" 200 -
2026-05-18 15:58:07,498 INFO 127.0.0.1 - - [18/May/2026 15:58:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:02:15,812 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 16:02:15,854 INFO * Restarting with stat
2026-05-18 16:02:17,699 WARNING * Debugger is active!
2026-05-18 16:02:17,738 INFO * Debugger PIN: 428-899-358
2026-05-18 16:02:25,166 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 16:02:25,202 INFO * Restarting with stat
2026-05-18 16:02:25,462 WARNING * Debugger is active!
2026-05-18 16:02:25,478 INFO * Debugger PIN: 428-899-358
2026-05-18 16:03:10,809 INFO 127.0.0.1 - - [18/May/2026 16:03:10] "GET / HTTP/1.1" 200 -
2026-05-18 16:03:10,847 INFO 127.0.0.1 - - [18/May/2026 16:03:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:03:19,126 INFO 127.0.0.1 - - [18/May/2026 16:03:19] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:03:19,154 INFO 127.0.0.1 - - [18/May/2026 16:03:19] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:03:39,945 INFO 127.0.0.1 - - [18/May/2026 16:03:39] "GET / HTTP/1.1" 200 -
2026-05-18 16:03:39,973 INFO 127.0.0.1 - - [18/May/2026 16:03:39] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:03:45,477 INFO 127.0.0.1 - - [18/May/2026 16:03:45] "GET /transactions/1/print HTTP/1.1" 200 -
2026-05-18 16:03:45,498 INFO 127.0.0.1 - - [18/May/2026 16:03:45] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:03:52,788 INFO 127.0.0.1 - - [18/May/2026 16:03:52] "GET / HTTP/1.1" 200 -
2026-05-18 16:03:52,811 INFO 127.0.0.1 - - [18/May/2026 16:03:52] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:06:56,872 INFO 127.0.0.1 - - [18/May/2026 16:06:56] "GET /users/new HTTP/1.1" 200 -
2026-05-18 16:06:56,906 INFO 127.0.0.1 - - [18/May/2026 16:06:56] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:06:59,528 INFO 127.0.0.1 - - [18/May/2026 16:06:59] "GET /assign HTTP/1.1" 200 -
2026-05-18 16:06:59,553 INFO 127.0.0.1 - - [18/May/2026 16:06:59] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:02,206 INFO 127.0.0.1 - - [18/May/2026 16:07:02] "GET /return HTTP/1.1" 200 -
2026-05-18 16:07:02,227 INFO 127.0.0.1 - - [18/May/2026 16:07:02] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:04,653 INFO 127.0.0.1 - - [18/May/2026 16:07:04] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 16:07:04,692 INFO 127.0.0.1 - - [18/May/2026 16:07:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:08,124 INFO 127.0.0.1 - - [18/May/2026 16:07:08] "GET / HTTP/1.1" 200 -
2026-05-18 16:07:08,155 INFO 127.0.0.1 - - [18/May/2026 16:07:08] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:14,597 INFO 127.0.0.1 - - [18/May/2026 16:07:14] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:07:14,608 INFO 127.0.0.1 - - [18/May/2026 16:07:14] "GET /login HTTP/1.1" 200 -
2026-05-18 16:07:14,638 INFO 127.0.0.1 - - [18/May/2026 16:07:14] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:14,643 INFO 127.0.0.1 - - [18/May/2026 16:07:14] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:07:21,945 INFO 127.0.0.1 - - [18/May/2026 16:07:21] "POST /login HTTP/1.1" 302 -
2026-05-18 16:07:21,962 INFO 127.0.0.1 - - [18/May/2026 16:07:21] "GET /set-password HTTP/1.1" 200 -
2026-05-18 16:07:22,077 INFO 127.0.0.1 - - [18/May/2026 16:07:22] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:22,090 INFO 127.0.0.1 - - [18/May/2026 16:07:22] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:07:30,789 INFO 127.0.0.1 - - [18/May/2026 16:07:30] "POST /set-password HTTP/1.1" 302 -
2026-05-18 16:07:30,820 INFO 127.0.0.1 - - [18/May/2026 16:07:30] "GET / HTTP/1.1" 200 -
2026-05-18 16:07:30,858 INFO 127.0.0.1 - - [18/May/2026 16:07:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:07:30,868 INFO 127.0.0.1 - - [18/May/2026 16:07:30] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:08:12,289 INFO 127.0.0.1 - - [18/May/2026 16:08:12] "GET / HTTP/1.1" 200 -
2026-05-18 16:08:12,319 INFO 127.0.0.1 - - [18/May/2026 16:08:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:08:17,486 INFO 127.0.0.1 - - [18/May/2026 16:08:17] "GET /users/new HTTP/1.1" 200 -
2026-05-18 16:08:17,508 INFO 127.0.0.1 - - [18/May/2026 16:08:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:08:18,649 INFO 127.0.0.1 - - [18/May/2026 16:08:18] "GET /assign HTTP/1.1" 200 -
2026-05-18 16:08:18,671 INFO 127.0.0.1 - - [18/May/2026 16:08:18] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:08:19,472 INFO 127.0.0.1 - - [18/May/2026 16:08:19] "GET /return HTTP/1.1" 200 -
2026-05-18 16:08:19,504 INFO 127.0.0.1 - - [18/May/2026 16:08:19] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:08:20,905 INFO 127.0.0.1 - - [18/May/2026 16:08:20] "GET / HTTP/1.1" 200 -
2026-05-18 16:08:20,936 INFO 127.0.0.1 - - [18/May/2026 16:08:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:10:20,468 INFO 127.0.0.1 - - [18/May/2026 16:10:20] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:10:20,479 INFO 127.0.0.1 - - [18/May/2026 16:10:20] "GET /login HTTP/1.1" 200 -
2026-05-18 16:10:20,505 INFO 127.0.0.1 - - [18/May/2026 16:10:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:10:20,524 INFO 127.0.0.1 - - [18/May/2026 16:10:20] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:12:56,071 INFO 127.0.0.1 - - [18/May/2026 16:12:56] "GET /login HTTP/1.1" 200 -
2026-05-18 16:12:56,094 INFO 127.0.0.1 - - [18/May/2026 16:12:56] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:12:58,093 INFO 127.0.0.1 - - [18/May/2026 16:12:58] "POST /login HTTP/1.1" 302 -
2026-05-18 16:12:58,124 INFO 127.0.0.1 - - [18/May/2026 16:12:58] "GET / HTTP/1.1" 200 -
2026-05-18 16:12:58,153 INFO 127.0.0.1 - - [18/May/2026 16:12:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:12:58,168 INFO 127.0.0.1 - - [18/May/2026 16:12:58] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:13:05,020 INFO 127.0.0.1 - - [18/May/2026 16:13:05] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:13:05,028 INFO 127.0.0.1 - - [18/May/2026 16:13:05] "GET /login HTTP/1.1" 200 -
2026-05-18 16:13:05,051 INFO 127.0.0.1 - - [18/May/2026 16:13:05] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:13:05,058 INFO 127.0.0.1 - - [18/May/2026 16:13:05] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:13:12,547 INFO 127.0.0.1 - - [18/May/2026 16:13:12] "POST /login HTTP/1.1" 302 -
2026-05-18 16:13:12,579 INFO 127.0.0.1 - - [18/May/2026 16:13:12] "GET / HTTP/1.1" 200 -
2026-05-18 16:13:12,610 INFO 127.0.0.1 - - [18/May/2026 16:13:12] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:13:12,620 INFO 127.0.0.1 - - [18/May/2026 16:13:12] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:13:20,417 INFO 127.0.0.1 - - [18/May/2026 16:13:20] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:13:20,423 INFO 127.0.0.1 - - [18/May/2026 16:13:20] "GET /login HTTP/1.1" 200 -
2026-05-18 16:13:20,451 INFO 127.0.0.1 - - [18/May/2026 16:13:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:13:20,455 INFO 127.0.0.1 - - [18/May/2026 16:13:20] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:13:28,041 INFO 127.0.0.1 - - [18/May/2026 16:13:28] "GET /login HTTP/1.1" 200 -
2026-05-18 16:13:28,066 INFO 127.0.0.1 - - [18/May/2026 16:13:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:13:30,934 INFO 127.0.0.1 - - [18/May/2026 16:13:30] "POST /login HTTP/1.1" 302 -
2026-05-18 16:13:30,965 INFO 127.0.0.1 - - [18/May/2026 16:13:30] "GET / HTTP/1.1" 200 -
2026-05-18 16:13:30,992 INFO 127.0.0.1 - - [18/May/2026 16:13:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:13:31,005 INFO 127.0.0.1 - - [18/May/2026 16:13:31] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:14:35,046 INFO 127.0.0.1 - - [18/May/2026 16:14:35] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:14:35,062 INFO 127.0.0.1 - - [18/May/2026 16:14:35] "GET /login HTTP/1.1" 200 -
2026-05-18 16:14:35,087 INFO 127.0.0.1 - - [18/May/2026 16:14:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:14:35,101 INFO 127.0.0.1 - - [18/May/2026 16:14:35] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:14:38,952 INFO 127.0.0.1 - - [18/May/2026 16:14:38] "POST /login HTTP/1.1" 302 -
2026-05-18 16:14:38,982 INFO 127.0.0.1 - - [18/May/2026 16:14:38] "GET / HTTP/1.1" 200 -
2026-05-18 16:14:39,014 INFO 127.0.0.1 - - [18/May/2026 16:14:39] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:14:39,027 INFO 127.0.0.1 - - [18/May/2026 16:14:39] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:14:44,356 INFO 127.0.0.1 - - [18/May/2026 16:14:44] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:14:44,369 INFO 127.0.0.1 - - [18/May/2026 16:14:44] "GET /login HTTP/1.1" 200 -
2026-05-18 16:14:44,402 INFO 127.0.0.1 - - [18/May/2026 16:14:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:14:44,406 INFO 127.0.0.1 - - [18/May/2026 16:14:44] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:15:21,266 INFO 127.0.0.1 - - [18/May/2026 16:15:21] "POST /login HTTP/1.1" 302 -
2026-05-18 16:15:21,306 INFO 127.0.0.1 - - [18/May/2026 16:15:21] "GET / HTTP/1.1" 200 -
2026-05-18 16:15:21,338 INFO 127.0.0.1 - - [18/May/2026 16:15:21] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:15:21,349 INFO 127.0.0.1 - - [18/May/2026 16:15:21] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:15:30,146 INFO 127.0.0.1 - - [18/May/2026 16:15:30] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:15:30,154 INFO 127.0.0.1 - - [18/May/2026 16:15:30] "GET /login HTTP/1.1" 200 -
2026-05-18 16:15:30,182 INFO 127.0.0.1 - - [18/May/2026 16:15:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:15:30,188 INFO 127.0.0.1 - - [18/May/2026 16:15:30] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:16:06,224 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 16:16:06,263 INFO * Restarting with stat
2026-05-18 16:16:06,588 WARNING * Debugger is active!
2026-05-18 16:16:06,605 INFO * Debugger PIN: 428-899-358
2026-05-18 16:16:37,079 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 16:16:37,115 INFO * Restarting with stat
2026-05-18 16:16:37,380 WARNING * Debugger is active!
2026-05-18 16:16:37,395 INFO * Debugger PIN: 428-899-358
2026-05-18 16:16:53,487 INFO 127.0.0.1 - - [18/May/2026 16:16:53] "POST /login HTTP/1.1" 302 -
2026-05-18 16:16:53,538 INFO 127.0.0.1 - - [18/May/2026 16:16:53] "GET / HTTP/1.1" 200 -
2026-05-18 16:16:53,578 INFO 127.0.0.1 - - [18/May/2026 16:16:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:16:53,585 INFO 127.0.0.1 - - [18/May/2026 16:16:53] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:17:15,730 INFO 127.0.0.1 - - [18/May/2026 16:17:15] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 16:17:15,754 INFO 127.0.0.1 - - [18/May/2026 16:17:15] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:17:17,183 INFO 127.0.0.1 - - [18/May/2026 16:17:17] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:17:17,193 INFO 127.0.0.1 - - [18/May/2026 16:17:17] "GET /login HTTP/1.1" 200 -
2026-05-18 16:17:17,214 INFO 127.0.0.1 - - [18/May/2026 16:17:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:17:17,222 INFO 127.0.0.1 - - [18/May/2026 16:17:17] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:18:55,617 INFO 127.0.0.1 - - [18/May/2026 16:18:55] "POST /login HTTP/1.1" 302 -
2026-05-18 16:18:55,659 INFO 127.0.0.1 - - [18/May/2026 16:18:55] "GET / HTTP/1.1" 200 -
2026-05-18 16:18:55,697 INFO 127.0.0.1 - - [18/May/2026 16:18:55] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:18:55,702 INFO 127.0.0.1 - - [18/May/2026 16:18:55] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:19:07,534 INFO 127.0.0.1 - - [18/May/2026 16:19:07] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:19:07,540 INFO 127.0.0.1 - - [18/May/2026 16:19:07] "GET /login HTTP/1.1" 200 -
2026-05-18 16:19:07,566 INFO 127.0.0.1 - - [18/May/2026 16:19:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:19:07,573 INFO 127.0.0.1 - - [18/May/2026 16:19:07] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:20:02,281 INFO 127.0.0.1 - - [18/May/2026 16:20:02] "POST /login HTTP/1.1" 302 -
2026-05-18 16:20:02,318 INFO 127.0.0.1 - - [18/May/2026 16:20:02] "GET / HTTP/1.1" 200 -
2026-05-18 16:20:02,349 INFO 127.0.0.1 - - [18/May/2026 16:20:02] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:20:02,364 INFO 127.0.0.1 - - [18/May/2026 16:20:02] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:21:41,997 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 16:21:42,035 INFO * Restarting with stat
2026-05-18 16:21:42,351 WARNING * Debugger is active!
2026-05-18 16:21:42,371 INFO * Debugger PIN: 428-899-358
2026-05-18 16:22:00,739 INFO 127.0.0.1 - - [18/May/2026 16:22:00] "GET / HTTP/1.1" 200 -
2026-05-18 16:22:00,765 INFO 127.0.0.1 - - [18/May/2026 16:22:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:22:03,798 INFO 127.0.0.1 - - [18/May/2026 16:22:03] "GET /logout HTTP/1.1" 302 -
2026-05-18 16:22:03,807 INFO 127.0.0.1 - - [18/May/2026 16:22:03] "GET /login HTTP/1.1" 200 -
2026-05-18 16:22:03,831 INFO 127.0.0.1 - - [18/May/2026 16:22:03] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:22:03,837 INFO 127.0.0.1 - - [18/May/2026 16:22:03] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:22:10,489 INFO 127.0.0.1 - - [18/May/2026 16:22:10] "POST /login HTTP/1.1" 302 -
2026-05-18 16:22:10,519 INFO 127.0.0.1 - - [18/May/2026 16:22:10] "GET / HTTP/1.1" 200 -
2026-05-18 16:22:10,551 INFO 127.0.0.1 - - [18/May/2026 16:22:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:22:10,565 INFO 127.0.0.1 - - [18/May/2026 16:22:10] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 16:27:08,992 INFO 127.0.0.1 - - [18/May/2026 16:27:08] "GET / HTTP/1.1" 200 -
2026-05-18 16:27:09,026 INFO 127.0.0.1 - - [18/May/2026 16:27:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:27:10,123 INFO 127.0.0.1 - - [18/May/2026 16:27:10] "GET / HTTP/1.1" 200 -
2026-05-18 16:27:10,143 INFO 127.0.0.1 - - [18/May/2026 16:27:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:27:10,720 INFO 127.0.0.1 - - [18/May/2026 16:27:10] "GET / HTTP/1.1" 200 -
2026-05-18 16:27:10,862 INFO 127.0.0.1 - - [18/May/2026 16:27:10] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 16:27:10,880 INFO 127.0.0.1 - - [18/May/2026 16:27:10] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 16:27:13,097 INFO 127.0.0.1 - - [18/May/2026 16:27:13] "GET / HTTP/1.1" 200 -
2026-05-18 16:27:13,227 INFO 127.0.0.1 - - [18/May/2026 16:27:13] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 16:27:13,246 INFO 127.0.0.1 - - [18/May/2026 16:27:13] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 16:27:14,395 INFO 127.0.0.1 - - [18/May/2026 16:27:14] "GET / HTTP/1.1" 200 -
2026-05-18 16:27:14,540 INFO 127.0.0.1 - - [18/May/2026 16:27:14] "GET /static/cancom.svg HTTP/1.1" 200 -
2026-05-18 16:27:14,559 INFO 127.0.0.1 - - [18/May/2026 16:27:14] "GET /static/favicon.ico HTTP/1.1" 200 -
2026-05-18 16:28:53,584 INFO 127.0.0.1 - - [18/May/2026 16:28:53] "GET / HTTP/1.1" 200 -
2026-05-18 16:28:53,607 INFO 127.0.0.1 - - [18/May/2026 16:28:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:32:27,829 INFO 127.0.0.1 - - [18/May/2026 16:32:27] "GET / HTTP/1.1" 200 -
2026-05-18 16:32:27,852 INFO 127.0.0.1 - - [18/May/2026 16:32:27] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:32:31,315 INFO 127.0.0.1 - - [18/May/2026 16:32:31] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:32:31,344 INFO 127.0.0.1 - - [18/May/2026 16:32:31] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:33:08,100 INFO 127.0.0.1 - - [18/May/2026 16:33:08] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:34:31,532 INFO 127.0.0.1 - - [18/May/2026 16:34:31] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:34:31,555 INFO 127.0.0.1 - - [18/May/2026 16:34:31] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:35:58,216 INFO 127.0.0.1 - - [18/May/2026 16:35:58] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:35:58,239 INFO 127.0.0.1 - - [18/May/2026 16:35:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:36:07,630 INFO 127.0.0.1 - - [18/May/2026 16:36:07] "GET / HTTP/1.1" 200 -
2026-05-18 16:36:07,658 INFO 127.0.0.1 - - [18/May/2026 16:36:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:36:09,450 INFO 127.0.0.1 - - [18/May/2026 16:36:09] "GET /transactions/3/print HTTP/1.1" 200 -
2026-05-18 16:36:09,478 INFO 127.0.0.1 - - [18/May/2026 16:36:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:38:45,796 INFO 127.0.0.1 - - [18/May/2026 16:38:45] "GET / HTTP/1.1" 200 -
2026-05-18 16:38:45,826 INFO 127.0.0.1 - - [18/May/2026 16:38:45] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:38:47,255 INFO 127.0.0.1 - - [18/May/2026 16:38:47] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:38:47,284 INFO 127.0.0.1 - - [18/May/2026 16:38:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:40:28,208 INFO 127.0.0.1 - - [18/May/2026 16:40:28] "GET / HTTP/1.1" 200 -
2026-05-18 16:40:28,239 INFO 127.0.0.1 - - [18/May/2026 16:40:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:40:29,482 INFO 127.0.0.1 - - [18/May/2026 16:40:29] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:40:29,515 INFO 127.0.0.1 - - [18/May/2026 16:40:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:41:35,309 INFO 127.0.0.1 - - [18/May/2026 16:41:35] "GET / HTTP/1.1" 200 -
2026-05-18 16:41:35,340 INFO 127.0.0.1 - - [18/May/2026 16:41:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:41:36,532 INFO 127.0.0.1 - - [18/May/2026 16:41:36] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:41:36,563 INFO 127.0.0.1 - - [18/May/2026 16:41:36] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:43:35,307 INFO 127.0.0.1 - - [18/May/2026 16:43:35] "GET / HTTP/1.1" 200 -
2026-05-18 16:43:35,339 INFO 127.0.0.1 - - [18/May/2026 16:43:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 16:43:38,425 INFO 127.0.0.1 - - [18/May/2026 16:43:38] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 16:43:38,450 INFO 127.0.0.1 - - [18/May/2026 16:43:38] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:39:04,523 INFO 127.0.0.1 - - [18/May/2026 19:39:04] "GET / HTTP/1.1" 200 -
2026-05-18 19:39:04,628 INFO 127.0.0.1 - - [18/May/2026 19:39:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:39:07,709 INFO 127.0.0.1 - - [18/May/2026 19:39:07] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:39:07,740 INFO 127.0.0.1 - - [18/May/2026 19:39:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:41:24,459 INFO 127.0.0.1 - - [18/May/2026 19:41:24] "GET / HTTP/1.1" 200 -
2026-05-18 19:41:24,488 INFO 127.0.0.1 - - [18/May/2026 19:41:24] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:41:27,973 INFO 127.0.0.1 - - [18/May/2026 19:41:27] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:41:28,005 INFO 127.0.0.1 - - [18/May/2026 19:41:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:43:07,610 INFO 127.0.0.1 - - [18/May/2026 19:43:07] "GET / HTTP/1.1" 200 -
2026-05-18 19:43:07,638 INFO 127.0.0.1 - - [18/May/2026 19:43:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:43:10,083 INFO 127.0.0.1 - - [18/May/2026 19:43:10] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:43:10,105 INFO 127.0.0.1 - - [18/May/2026 19:43:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:45:21,476 INFO 127.0.0.1 - - [18/May/2026 19:45:21] "GET / HTTP/1.1" 200 -
2026-05-18 19:45:21,505 INFO 127.0.0.1 - - [18/May/2026 19:45:21] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:45:24,274 INFO 127.0.0.1 - - [18/May/2026 19:45:24] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:45:24,303 INFO 127.0.0.1 - - [18/May/2026 19:45:24] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:45:27,394 INFO 127.0.0.1 - - [18/May/2026 19:45:27] "GET / HTTP/1.1" 200 -
2026-05-18 19:45:27,417 INFO 127.0.0.1 - - [18/May/2026 19:45:27] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:45:29,529 INFO 127.0.0.1 - - [18/May/2026 19:45:29] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:45:29,555 INFO 127.0.0.1 - - [18/May/2026 19:45:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:47:04,445 INFO 127.0.0.1 - - [18/May/2026 19:47:04] "GET / HTTP/1.1" 200 -
2026-05-18 19:47:04,472 INFO 127.0.0.1 - - [18/May/2026 19:47:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:47:06,739 INFO 127.0.0.1 - - [18/May/2026 19:47:06] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:47:06,769 INFO 127.0.0.1 - - [18/May/2026 19:47:06] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:48:07,688 INFO 127.0.0.1 - - [18/May/2026 19:48:07] "GET / HTTP/1.1" 200 -
2026-05-18 19:48:07,713 INFO 127.0.0.1 - - [18/May/2026 19:48:07] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:48:09,135 INFO 127.0.0.1 - - [18/May/2026 19:48:09] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:48:09,155 INFO 127.0.0.1 - - [18/May/2026 19:48:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:49:17,078 INFO 127.0.0.1 - - [18/May/2026 19:49:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:49:39,165 INFO 127.0.0.1 - - [18/May/2026 19:49:39] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:49:44,653 INFO 127.0.0.1 - - [18/May/2026 19:49:44] "GET / HTTP/1.1" 200 -
2026-05-18 19:49:44,684 INFO 127.0.0.1 - - [18/May/2026 19:49:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:49:46,033 INFO 127.0.0.1 - - [18/May/2026 19:49:46] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:49:46,056 INFO 127.0.0.1 - - [18/May/2026 19:49:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:52:53,735 INFO 127.0.0.1 - - [18/May/2026 19:52:53] "GET / HTTP/1.1" 200 -
2026-05-18 19:52:53,763 INFO 127.0.0.1 - - [18/May/2026 19:52:53] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:52:55,021 INFO 127.0.0.1 - - [18/May/2026 19:52:55] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 19:52:55,051 INFO 127.0.0.1 - - [18/May/2026 19:52:55] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:53:13,216 INFO 127.0.0.1 - - [18/May/2026 19:53:13] "GET / HTTP/1.1" 200 -
2026-05-18 19:53:13,246 INFO 127.0.0.1 - - [18/May/2026 19:53:13] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:55:55,995 INFO 127.0.0.1 - - [18/May/2026 19:55:55] "GET / HTTP/1.1" 200 -
2026-05-18 19:55:56,018 INFO 127.0.0.1 - - [18/May/2026 19:55:56] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:58:45,997 INFO 127.0.0.1 - - [18/May/2026 19:58:45] "GET / HTTP/1.1" 200 -
2026-05-18 19:58:46,020 INFO 127.0.0.1 - - [18/May/2026 19:58:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 19:59:36,278 INFO 127.0.0.1 - - [18/May/2026 19:59:36] "GET / HTTP/1.1" 200 -
2026-05-18 19:59:36,301 INFO 127.0.0.1 - - [18/May/2026 19:59:36] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:34:14,764 INFO 127.0.0.1 - - [18/May/2026 20:34:14] "GET / HTTP/1.1" 200 -
2026-05-18 20:34:14,806 INFO 127.0.0.1 - - [18/May/2026 20:34:14] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:34:23,677 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
2026-05-18 20:34:23,677 INFO Press CTRL+C to quit
2026-05-18 20:34:23,681 INFO * Restarting with stat
2026-05-18 20:34:23,902 WARNING * Debugger is active!
2026-05-18 20:34:23,920 INFO * Debugger PIN: 428-899-358
2026-05-18 20:38:11,471 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 20:38:11,511 INFO * Restarting with stat
2026-05-18 20:38:11,838 WARNING * Debugger is active!
2026-05-18 20:38:11,866 INFO * Debugger PIN: 428-899-358
2026-05-18 20:40:33,937 INFO 127.0.0.1 - - [18/May/2026 20:40:33] "GET / HTTP/1.1" 200 -
2026-05-18 20:40:33,994 INFO 127.0.0.1 - - [18/May/2026 20:40:33] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:40:41,541 INFO 127.0.0.1 - - [18/May/2026 20:40:41] "GET /admin/import HTTP/1.1" 200 -
2026-05-18 20:40:41,564 INFO 127.0.0.1 - - [18/May/2026 20:40:41] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:43:25,032 INFO 127.0.0.1 - - [18/May/2026 20:43:25] "POST /admin/import HTTP/1.1" 302 -
2026-05-18 20:43:25,041 INFO 127.0.0.1 - - [18/May/2026 20:43:25] "GET /admin/import HTTP/1.1" 200 -
2026-05-18 20:43:25,075 INFO 127.0.0.1 - - [18/May/2026 20:43:25] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:43:25,087 INFO 127.0.0.1 - - [18/May/2026 20:43:25] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 20:44:03,384 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 20:44:03,422 INFO * Restarting with stat
2026-05-18 20:44:03,768 WARNING * Debugger is active!
2026-05-18 20:44:03,787 INFO * Debugger PIN: 428-899-358
2026-05-18 20:45:25,715 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 20:45:25,753 INFO * Restarting with stat
2026-05-18 20:45:27,192 WARNING * Debugger is active!
2026-05-18 20:45:27,225 INFO * Debugger PIN: 428-899-358
2026-05-18 20:46:16,616 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 20:46:16,648 INFO * Restarting with stat
2026-05-18 20:46:17,008 WARNING * Debugger is active!
2026-05-18 20:46:17,025 INFO * Debugger PIN: 428-899-358
2026-05-18 20:46:54,852 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-18 20:46:54,888 INFO * Restarting with stat
2026-05-18 20:46:55,230 WARNING * Debugger is active!
2026-05-18 20:46:55,252 INFO * Debugger PIN: 428-899-358
2026-05-18 20:47:20,700 INFO 127.0.0.1 - - [18/May/2026 20:47:20] "GET /admin/import HTTP/1.1" 200 -
2026-05-18 20:47:20,732 INFO 127.0.0.1 - - [18/May/2026 20:47:20] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:47:40,793 INFO 127.0.0.1 - - [18/May/2026 20:47:40] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 20:47:40,818 INFO 127.0.0.1 - - [18/May/2026 20:47:40] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:47:46,284 INFO 127.0.0.1 - - [18/May/2026 20:47:46] "GET /return HTTP/1.1" 200 -
2026-05-18 20:47:46,306 INFO 127.0.0.1 - - [18/May/2026 20:47:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:47:47,796 INFO 127.0.0.1 - - [18/May/2026 20:47:47] "GET /assign HTTP/1.1" 200 -
2026-05-18 20:47:47,819 INFO 127.0.0.1 - - [18/May/2026 20:47:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:47:50,031 INFO 127.0.0.1 - - [18/May/2026 20:47:50] "GET /users/new HTTP/1.1" 200 -
2026-05-18 20:47:50,055 INFO 127.0.0.1 - - [18/May/2026 20:47:50] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:00,202 INFO 127.0.0.1 - - [18/May/2026 20:48:00] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:00,233 INFO 127.0.0.1 - - [18/May/2026 20:48:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:23,087 INFO 127.0.0.1 - - [18/May/2026 20:48:23] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:23,112 INFO 127.0.0.1 - - [18/May/2026 20:48:23] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:26,546 INFO 127.0.0.1 - - [18/May/2026 20:48:26] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:26,566 INFO 127.0.0.1 - - [18/May/2026 20:48:26] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:27,384 INFO 127.0.0.1 - - [18/May/2026 20:48:27] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:27,404 INFO 127.0.0.1 - - [18/May/2026 20:48:27] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:28,020 INFO 127.0.0.1 - - [18/May/2026 20:48:28] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:28,041 INFO 127.0.0.1 - - [18/May/2026 20:48:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:28,663 INFO 127.0.0.1 - - [18/May/2026 20:48:28] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:28,681 INFO 127.0.0.1 - - [18/May/2026 20:48:28] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:29,491 INFO 127.0.0.1 - - [18/May/2026 20:48:29] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:29,512 INFO 127.0.0.1 - - [18/May/2026 20:48:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:43,006 INFO 127.0.0.1 - - [18/May/2026 20:48:43] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:43,028 INFO 127.0.0.1 - - [18/May/2026 20:48:43] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:43,800 INFO 127.0.0.1 - - [18/May/2026 20:48:43] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:43,821 INFO 127.0.0.1 - - [18/May/2026 20:48:43] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:44,531 INFO 127.0.0.1 - - [18/May/2026 20:48:44] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:44,548 INFO 127.0.0.1 - - [18/May/2026 20:48:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:45,340 INFO 127.0.0.1 - - [18/May/2026 20:48:45] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:45,362 INFO 127.0.0.1 - - [18/May/2026 20:48:45] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:46,156 INFO 127.0.0.1 - - [18/May/2026 20:48:46] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:46,175 INFO 127.0.0.1 - - [18/May/2026 20:48:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:47,169 INFO 127.0.0.1 - - [18/May/2026 20:48:47] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:47,189 INFO 127.0.0.1 - - [18/May/2026 20:48:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:47,910 INFO 127.0.0.1 - - [18/May/2026 20:48:47] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:47,929 INFO 127.0.0.1 - - [18/May/2026 20:48:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:48,678 INFO 127.0.0.1 - - [18/May/2026 20:48:48] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:48,697 INFO 127.0.0.1 - - [18/May/2026 20:48:48] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:56,021 INFO 127.0.0.1 - - [18/May/2026 20:48:56] "GET / HTTP/1.1" 200 -
2026-05-18 20:48:56,039 INFO 127.0.0.1 - - [18/May/2026 20:48:56] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:59,558 INFO 127.0.0.1 - - [18/May/2026 20:48:59] "GET /logout HTTP/1.1" 302 -
2026-05-18 20:48:59,567 INFO 127.0.0.1 - - [18/May/2026 20:48:59] "GET /login HTTP/1.1" 200 -
2026-05-18 20:48:59,587 INFO 127.0.0.1 - - [18/May/2026 20:48:59] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:48:59,600 INFO 127.0.0.1 - - [18/May/2026 20:48:59] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 20:49:09,950 INFO 127.0.0.1 - - [18/May/2026 20:49:09] "POST /login HTTP/1.1" 302 -
2026-05-18 20:49:09,983 INFO 127.0.0.1 - - [18/May/2026 20:49:09] "GET / HTTP/1.1" 200 -
2026-05-18 20:49:10,015 INFO 127.0.0.1 - - [18/May/2026 20:49:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 20:49:10,030 INFO 127.0.0.1 - - [18/May/2026 20:49:10] "GET /static/favicon.ico HTTP/1.1" 304 -
2026-05-18 20:56:44,123 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
2026-05-18 20:56:44,125 INFO Press CTRL+C to quit
2026-05-18 20:56:44,126 INFO * Restarting with stat
2026-05-18 20:56:44,370 WARNING * Debugger is active!
2026-05-18 20:56:44,388 INFO * Debugger PIN: 428-899-358
2026-05-18 21:00:29,403 INFO 127.0.0.1 - - [18/May/2026 21:00:29] "GET / HTTP/1.1" 200 -
2026-05-18 21:00:29,431 INFO 127.0.0.1 - - [18/May/2026 21:00:29] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:30,119 INFO 127.0.0.1 - - [18/May/2026 21:00:30] "GET / HTTP/1.1" 200 -
2026-05-18 21:00:30,140 INFO 127.0.0.1 - - [18/May/2026 21:00:30] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:46,795 INFO 127.0.0.1 - - [18/May/2026 21:00:46] "GET /users/new HTTP/1.1" 200 -
2026-05-18 21:00:46,818 INFO 127.0.0.1 - - [18/May/2026 21:00:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:48,123 INFO 127.0.0.1 - - [18/May/2026 21:00:48] "GET /assign HTTP/1.1" 200 -
2026-05-18 21:00:48,145 INFO 127.0.0.1 - - [18/May/2026 21:00:48] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:49,862 INFO 127.0.0.1 - - [18/May/2026 21:00:49] "GET /return HTTP/1.1" 200 -
2026-05-18 21:00:49,893 INFO 127.0.0.1 - - [18/May/2026 21:00:49] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:55,336 INFO 127.0.0.1 - - [18/May/2026 21:00:55] "GET /admin/import HTTP/1.1" 200 -
2026-05-18 21:00:55,364 INFO 127.0.0.1 - - [18/May/2026 21:00:55] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:00:58,118 INFO 127.0.0.1 - - [18/May/2026 21:00:58] "GET /admin/staff HTTP/1.1" 200 -
2026-05-18 21:00:58,144 INFO 127.0.0.1 - - [18/May/2026 21:00:58] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:01:00,607 INFO 127.0.0.1 - - [18/May/2026 21:01:00] "GET /users/new HTTP/1.1" 200 -
2026-05-18 21:01:00,631 INFO 127.0.0.1 - - [18/May/2026 21:01:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:01:02,294 INFO 127.0.0.1 - - [18/May/2026 21:01:02] "GET / HTTP/1.1" 200 -
2026-05-18 21:01:02,315 INFO 127.0.0.1 - - [18/May/2026 21:01:02] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:01:09,803 INFO 127.0.0.1 - - [18/May/2026 21:01:09] "GET /transactions/4/print HTTP/1.1" 200 -
2026-05-18 21:01:09,828 INFO 127.0.0.1 - - [18/May/2026 21:01:09] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:01:47,672 INFO 127.0.0.1 - - [18/May/2026 21:01:47] "GET / HTTP/1.1" 200 -
2026-05-18 21:01:47,698 INFO 127.0.0.1 - - [18/May/2026 21:01:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:03:19,621 INFO 127.0.0.1 - - [18/May/2026 21:03:19] "GET / HTTP/1.1" 200 -
2026-05-18 21:03:19,647 INFO 127.0.0.1 - - [18/May/2026 21:03:19] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:04:59,841 INFO 127.0.0.1 - - [18/May/2026 21:04:59] "GET / HTTP/1.1" 200 -
2026-05-18 21:04:59,864 INFO 127.0.0.1 - - [18/May/2026 21:04:59] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:07:00,533 INFO 127.0.0.1 - - [18/May/2026 21:07:00] "GET / HTTP/1.1" 200 -
2026-05-18 21:07:00,556 INFO 127.0.0.1 - - [18/May/2026 21:07:00] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:07:14,520 INFO 127.0.0.1 - - [18/May/2026 21:07:14] "GET /users/new HTTP/1.1" 200 -
2026-05-18 21:07:14,547 INFO 127.0.0.1 - - [18/May/2026 21:07:14] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:08:35,615 INFO 127.0.0.1 - - [18/May/2026 21:08:35] "GET / HTTP/1.1" 200 -
2026-05-18 21:08:35,653 INFO 127.0.0.1 - - [18/May/2026 21:08:35] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:09:44,487 INFO 127.0.0.1 - - [18/May/2026 21:09:44] "GET / HTTP/1.1" 200 -
2026-05-18 21:09:44,511 INFO 127.0.0.1 - - [18/May/2026 21:09:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:09:46,147 INFO 127.0.0.1 - - [18/May/2026 21:09:46] "GET / HTTP/1.1" 200 -
2026-05-18 21:09:46,169 INFO 127.0.0.1 - - [18/May/2026 21:09:46] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:10:10,350 INFO 127.0.0.1 - - [18/May/2026 21:10:10] "GET / HTTP/1.1" 200 -
2026-05-18 21:10:10,369 INFO 127.0.0.1 - - [18/May/2026 21:10:10] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:11:01,721 INFO 127.0.0.1 - - [18/May/2026 21:11:01] "GET / HTTP/1.1" 200 -
2026-05-18 21:11:01,744 INFO 127.0.0.1 - - [18/May/2026 21:11:01] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-18 21:12:05,298 INFO 127.0.0.1 - - [18/May/2026 21:12:05] "GET / HTTP/1.1" 200 -
2026-05-18 21:12:05,322 INFO 127.0.0.1 - - [18/May/2026 21:12:05] "GET /static/cancom.svg HTTP/1.1" 304 -

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
Flask==3.0.3

4408
session-ses_1c3a.md Normal file

File diff suppressed because one or more lines are too long

4
static/cancom.svg Executable file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<!-- Created with xTool Creative Space (https://www.xtool.com/pages/software) -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:xcs="https://www.xtool.com/pages/software" version="1.1" preserveAspectRatio="xMinYMin meet" width="61.682394106284505mm" height="9.909010347638684mm" viewBox="136.55880294685778 209.2954948261807 61.682394106284505 9.909010347638684" xcs:version="2.7.22"><style></style>
<path transform="matrix(0.158038,0,0,0.158038,133.2558,163.322122)" stroke="none" fill="#ffffff" data-view-type="laser" d="M125.9,352.1h18.9L119,291.7h-16.2l-25,60.4h18.2l14.6-38.9L125.9,352.1z M323.6,322 c0,8.8-6.1,16.4-15.4,16.4c-9.1,0-15.4-7.6-15.4-16.4s6.1-16.4,15.2-16.4C317,305.6,323.6,313.4,323.6,322 M340.5,322.3 c0-17.4-13.4-31.3-32.3-31.3c-18.7,0-32.3,14.1-32.3,31.3c0,17.4,13.4,31.3,32.3,31.3C326.9,353.6,340.5,339.4,340.5,322.3 M230.4,322.5c0-8.8,6.1-16.4,14.9-16.4c3,0,5.8,0.8,8.1,2l6.6-13.4c-3-2-8.1-3.8-14.9-3.8c-18.2,0-31.6,14.1-31.6,31.3 c0,18.2,12.6,31.1,31.6,31.1c17.2,0,26-10.6,28.8-21l-13.6-5.6c-1.8,5.8-6.3,11.6-14.4,11.6C236.5,338.4,230.4,331.4,230.4,322.5 M38,322.5c0-8.8,6.1-16.4,14.9-16.4c3,0,5.8,0.8,8.1,2l6.6-13.4c-3-2-8.1-3.8-14.9-3.8c-18.4,0-31.8,14.1-31.8,31.3 c0,18.2,12.6,31.1,31.6,31.1c17.2,0,26-10.6,28.8-21l-13.6-5.6c-1.8,5.8-6.3,11.6-14.4,11.6C44,338.4,38,331.4,38,322.5 M191.8,352.1h14.4v-59.8H190v33.1l-25-33.1h-15.4v60.1h16.2v-34.1L191.8,352.1z M411.2,352.1v-59.8h-17.7l-14.4,23.2l-14.4-23.2 h-17.7v60.1h16.4V318l15.4,23.2h0.3l15.4-23.5v34.6L411.2,352.1L411.2,352.1z" fill-rule="nonzero"></path></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
static/favicon.ico Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,37 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>Ausgabe</h1>
<p>Tuerchips und Parkkarten an bestehende User ausgeben.</p>
</div>
<section class="card form-card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-key me-1"></i>Chip oder Parkkarte ausgeben</h2>
<form method="post" action="{{ url_for('assign_asset') }}">
<div class="mb-3">
<label class="form-label">User</label>
<select class="form-select" name="user_id" required>
<option value="">Bitte waehlen</option>
{% for user in users %}
<option value="{{ user.id }}">{{ user.full_name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Typ</label>
<select class="form-select" name="asset_type" required>
<option value="chip">Tuerchip</option>
<option value="parking_card">Parkkarte</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Kennung</label>
<input class="form-control" type="text" name="asset_code" required placeholder="z. B. CHIP-1001">
</div>
<button class="btn btn-primary" type="submit"><i class="ti ti-check me-1"></i>Ausgabe speichern</button>
</form>
</div>
</section>
{% endblock %}

561
templates/base.html Normal file
View File

@@ -0,0 +1,561 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title or "Chipverwaltung" }}</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="stylesheet" href="https://unpkg.com/@tabler/core@1.0.0-beta20/dist/css/tabler.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
<style>
:root {
--ccm-bg: #f4f6f8;
--ccm-text: #212121;
--ccm-primary: #da002d;
--ccm-primary-hover: #b00024;
--ccm-header: #2b2f36;
--ccm-surface: #ffffff;
--ccm-border: #d9dee3;
--ccm-muted: #6b7280;
}
html {
font-size: 16px;
}
body {
background-color: var(--ccm-bg);
color: var(--ccm-text);
font-family: "Segoe UI", Arial, sans-serif;
}
h1,
h2,
h3,
h4,
h5,
h6,
.card-title,
.navbar-brand-wordmark strong {
font-weight: 600;
}
.navbar-brand-wordmark {
display: flex;
flex-direction: column;
line-height: 1.1;
font-size: 0.92rem;
}
.navbar-brand-logo {
height: 1.85rem;
width: auto;
display: block;
}
.brand-surface {
background-color: var(--ccm-header);
margin-bottom: 0;
border-bottom: 0;
box-shadow: none;
}
.brand-surface .navbar-brand-wordmark,
.brand-surface .nav-link,
.brand-surface .navbar-nav .btn {
color: #ffffff;
}
.brand-surface .btn-outline-secondary {
border-color: rgba(255, 255, 255, 0.35);
color: #ffffff;
}
.brand-surface .btn-outline-secondary:hover,
.brand-surface .btn-outline-secondary:focus {
background-color: rgba(255, 255, 255, 0.12);
border-color: rgba(255, 255, 255, 0.5);
color: #ffffff;
}
.navbar-brand-wordmark strong {
letter-spacing: 0.06em;
text-transform: uppercase;
}
.app-shell {
width: 100%;
max-width: 1440px;
margin: 0 auto;
padding: 2rem 1.5rem 2.5rem;
}
.auth-shell {
min-height: calc(100vh - 72px);
display: flex;
align-items: center;
justify-content: center;
padding: 2rem 1.5rem;
}
.page-shell {
width: 100%;
max-width: 1320px;
margin: 0 auto;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.25rem;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
}
.card.stat-card {
border: 0;
color: #ffffff;
border-radius: 1rem;
overflow: hidden;
box-shadow: 0 18px 34px rgba(43, 47, 54, 0.14);
}
.card.stat-card .card-body {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 1.25rem;
}
.card.stat-card .stat-label {
font-size: 0.875rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
opacity: 0.92;
}
.card.stat-card .stat-value {
font-size: 2.25rem;
font-weight: 700;
line-height: 1;
margin-top: 0.35rem;
}
.card.stat-card .stat-icon {
width: 3rem;
height: 3rem;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 999px;
background: rgba(255, 255, 255, 0.18);
font-size: 1.5rem;
}
.card.stat-card.stat-card-users {
background: linear-gradient(135deg, #da002d 0%, #b00024 100%);
color: #ffffff;
}
.card.stat-card.stat-card-chips {
background: linear-gradient(135deg, #2b2f36 0%, #434a54 100%);
color: #ffffff;
}
.card.stat-card.stat-card-cards {
background: linear-gradient(135deg, #8c0f2b 0%, #da002d 100%);
color: #ffffff;
}
.btn-primary {
background-color: var(--ccm-primary);
border-color: var(--ccm-primary);
}
.btn-primary:hover,
.btn-primary:focus {
background-color: var(--ccm-primary-hover);
border-color: var(--ccm-primary-hover);
}
.nav-surface {
background-color: var(--ccm-primary);
margin-top: 0;
border-top: 0;
box-shadow: none;
}
.navbar-expand-md,
.navbar-collapse,
.navbar-collapse .navbar {
margin-top: 0;
border-top: 0;
box-shadow: none;
}
header.navbar,
.navbar-dark.nav-surface,
.collapse.navbar-collapse.show {
border: 0;
box-shadow: none;
}
.nav-surface .btn {
min-width: 132px;
}
.nav-surface .btn-white {
color: var(--ccm-primary);
border-color: rgba(255, 255, 255, 0.9);
}
.nav-surface .btn-white:hover,
.nav-surface .btn-white:focus {
background-color: #e9ecef;
border-color: #e9ecef;
color: var(--ccm-primary-hover);
}
.nav-surface .btn.active {
background-color: #ffffff;
border-color: #ffffff;
color: var(--ccm-primary-hover);
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.03);
}
.flash-stack {
display: grid;
gap: 0.75rem;
position: fixed;
top: 8.5rem;
right: 1rem;
width: min(420px, calc(100vw - 2rem));
z-index: 1080;
}
@media (max-width: 767.98px) {
.flash-stack {
top: 7.25rem;
}
}
.table-wrap {
overflow-x: auto;
}
.card-table .btn {
white-space: nowrap;
}
.muted {
color: var(--ccm-muted);
}
.top-actions a {
text-decoration: none;
}
.btn .ti,
.nav-surface .ti {
font-size: 1rem;
vertical-align: -2px;
}
.login-card {
width: 100%;
max-width: 720px;
}
.content-stack {
width: 100%;
max-width: 1320px;
margin: 0 auto;
}
.content-stack-wide {
width: 100%;
max-width: 1360px;
margin: 0 auto;
}
.form-card {
width: 100%;
max-width: none;
margin: 0 auto;
}
.card {
border: 1px solid var(--ccm-border);
border-radius: 1rem;
box-shadow: 0 12px 30px rgba(43, 47, 54, 0.06);
background: var(--ccm-surface);
}
.card-header {
background: transparent;
border-bottom: 1px solid var(--ccm-border);
padding: 1rem 1.25rem;
}
.card-body {
padding: 1.25rem;
}
.card-title {
margin: 0;
color: var(--ccm-text);
}
.form-label {
color: var(--ccm-text);
font-weight: 600;
}
.form-control,
.form-select {
border-color: var(--ccm-border);
min-height: 2.75rem;
}
.form-control:focus,
.form-select:focus {
border-color: rgba(218, 0, 45, 0.45);
box-shadow: 0 0 0 0.2rem rgba(218, 0, 45, 0.12);
}
.table {
--tblr-table-striped-bg: #fafbfc;
}
.table thead th {
color: var(--ccm-muted);
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.03em;
text-transform: uppercase;
}
.alert-info {
color: #5d0016;
background: rgba(218, 0, 45, 0.08);
border: 1px solid rgba(218, 0, 45, 0.16);
box-shadow: 0 12px 28px rgba(43, 47, 54, 0.14);
animation: fadeout 0.35s ease forwards;
animation-delay: 3.5s;
}
.alert-success {
color: #0f5132;
background: #d1e7dd;
border: 1px solid #badbcc;
box-shadow: 0 12px 28px rgba(43, 47, 54, 0.14);
animation: fadeout 0.35s ease forwards;
animation-delay: 3.5s;
}
.alert-error,
.alert-danger {
color: #842029;
background: #f8d7da;
border: 1px solid #f5c2c7;
box-shadow: 0 12px 28px rgba(43, 47, 54, 0.14);
}
.alert-import-errors {
color: #842029;
background: #fff5f5;
border: 1px solid #f1b0b7;
box-shadow: 0 14px 30px rgba(43, 47, 54, 0.16);
max-height: min(70vh, 36rem);
overflow: auto;
}
.flash-list {
margin: 0.75rem 0 0;
padding-left: 1.25rem;
}
.flash-list li + li {
margin-top: 0.35rem;
}
@keyframes fadeout {
to {
opacity: 0;
transform: translateY(-8px);
pointer-events: none;
}
}
.alert-dismissible .btn-close {
padding: 1rem;
}
.page-section-title {
margin-bottom: 1rem;
}
.page-section-title h1 {
margin: 0;
font-size: 1.75rem;
}
.page-section-title p {
margin: 0.35rem 0 0;
color: var(--ccm-muted);
}
.app-footer {
margin-top: 1rem;
padding: 0.65rem 1.5rem 0.95rem;
color: var(--ccm-muted);
font-size: 0.82rem;
background: transparent;
}
.app-footer-inner {
width: 100%;
max-width: 1320px;
margin: 0 auto;
display: flex;
flex-wrap: wrap;
gap: 0.2rem 0.75rem;
justify-content: flex-start;
align-items: center;
text-align: left;
}
.app-footer-separator {
color: #b0b7c3;
display: none;
}
@media (max-width: 767.98px) {
.card-header,
.card-body {
padding: 1rem;
}
.table thead th,
.table tbody td {
white-space: nowrap;
}
.nav-surface .btn {
min-width: 0;
}
}
</style>
</head>
<body>
<div class="page">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-stack">
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible" role="alert">
{% if category == 'import-errors' %}
<strong>Import konnte nicht ausgefuehrt werden.</strong>
<ul class="flash-list mb-0">
{% for entry in message %}
<li>{{ entry }}</li>
{% endfor %}
</ul>
{% else %}
{{ message }}
{% endif %}
<button type="button" class="btn-close" aria-label="Schliessen" onclick="this.parentElement.remove();"></button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<header class="navbar navbar-expand-md d-print-none brand-surface">
<div class="container-xl">
<div class="navbar-brand navbar-brand-autodark pe-0 pe-md-3">
<img class="navbar-brand-logo me-3" src="{{ url_for('static', filename='cancom.svg') }}" alt="CANCOM Logo">
<span class="navbar-brand-wordmark">
<strong>CANCOM BU Sued/West</strong>
<span>Tuerchip und Parkkartenverwaltung</span>
</span>
</div>
<div class="navbar-nav flex-row order-md-last top-actions">
{% if g.current_staff %}
<span class="nav-link disabled">{{ g.current_staff.full_name }} ({{ g.current_staff.role }})</span>
<a class="btn btn-outline-secondary" href="{{ url_for('logout') }}"><i class="ti ti-logout me-1"></i>Abmelden</a>
{% else %}
<a class="btn btn-primary" href="{{ url_for('login') }}"><i class="ti ti-login me-1"></i>Login</a>
{% endif %}
</div>
</div>
</header>
{% if g.current_staff %}
<div class="navbar-expand-md">
<div class="collapse navbar-collapse show">
<div class="navbar navbar-dark nav-surface">
<div class="container-xl">
<div class="d-flex flex-wrap gap-2 py-2">
<a class="btn btn-white {% if request.endpoint == 'index' %}active{% endif %}" href="{{ url_for('index') }}"><i class="ti ti-layout-dashboard me-1"></i>Uebersicht</a>
<a class="btn btn-white {% if request.endpoint == 'create_user' %}active{% endif %}" href="{{ url_for('create_user') }}"><i class="ti ti-user-plus me-1"></i>User anlegen</a>
<a class="btn btn-white {% if request.endpoint == 'assign_asset' %}active{% endif %}" href="{{ url_for('assign_asset') }}"><i class="ti ti-key me-1"></i>Ausgabe</a>
<a class="btn btn-white {% if request.endpoint == 'return_asset' %}active{% endif %}" href="{{ url_for('return_asset') }}"><i class="ti ti-arrow-back-up me-1"></i>Rueckgabe</a>
{% if g.current_staff.role == 'admin' %}
<a class="btn btn-white {% if request.endpoint == 'import_data' %}active{% endif %}" href="{{ url_for('import_data') }}"><i class="ti ti-file-import me-1"></i>Import</a>
<a class="btn btn-white {% if request.endpoint == 'manage_staff' %}active{% endif %}" href="{{ url_for('manage_staff') }}"><i class="ti ti-users-group me-1"></i>Bearbeiterverwaltung</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endif %}
<div class="page-wrapper">
<div class="{% if request.endpoint == 'login' %}auth-shell{% else %}app-shell{% endif %}">
<div class="{% if request.endpoint == 'login' %}login-card{% else %}page-shell{% endif %}">
{% block content %}{% endblock %}
</div>
</div>
</div>
<footer class="app-footer">
<div class="app-footer-inner">
<span>Autor Erik Thiele</span>
<span class="app-footer-separator">|</span>
<span>&copy; {{ app_year }} CANCOM GmbH</span>
<span class="app-footer-separator">|</span>
<span>Version {{ app_version }}</span>
<span class="app-footer-separator">|</span>
<span>Host {{ app_host }}</span>
</div>
</footer>
</div>
<script>
window.addEventListener("load", () => {
window.setTimeout(() => {
document.querySelectorAll(".flash-stack .alert").forEach((element) => {
if (element.classList.contains("alert-error") || element.classList.contains("alert-danger") || element.classList.contains("alert-import-errors")) {
return;
}
window.setTimeout(() => {
element.remove();
}, 500);
});
}, 4000);
});
</script>
</body>
</html>

View File

@@ -0,0 +1,29 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>User anlegen</h1>
<p>Neue Anwender fuer die Verwaltung von Tuerchips und Parkkarten erfassen.</p>
</div>
<section class="card form-card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-user-plus me-1"></i>Neuen User anlegen</h2>
<form method="post" action="{{ url_for('create_user') }}">
<div class="mb-3">
<label class="form-label">Vollstaendiger Name</label>
<input class="form-control" type="text" name="full_name" required>
</div>
<div class="mb-3">
<label class="form-label">E-Mail</label>
<input class="form-control" type="email" name="email">
</div>
<div class="mb-3">
<label class="form-label">Abteilung</label>
<input class="form-control" type="text" name="department">
</div>
<button class="btn btn-primary" type="submit"><i class="ti ti-device-floppy me-1"></i>User speichern</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>Import</h1>
<p>Vorhandene Bestandsdaten zeilenweise in die Datenbank uebernehmen.</p>
</div>
<section class="card form-card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-file-import me-1"></i>Importdaten einfuegen</h2>
<p class="muted">CSV-Format pro Zeile: <code>User;Typ;Kennung;Aktion</code>. Fuer die Aktion ist <code>Import</code> vorgesehen.</p>
<form method="post" action="{{ url_for('import_data') }}" enctype="multipart/form-data">
<div class="mb-3">
<label class="form-label">CSV-Datei</label>
<input class="form-control" type="file" name="import_file" accept=".csv,text/csv">
<div class="form-hint">UTF-8, Semikolon-getrennt. Wenn eine Datei ausgewaehlt ist, wird sie bevorzugt importiert.</div>
</div>
<div class="mb-3">
<label class="form-label">Importzeilen alternativ direkt einfuegen</label>
<textarea class="form-control" name="import_rows" rows="12" placeholder="Max Mustermann;Tuerchip;CHIP-1001;Import&#10;Erika Muster;Parkkarte;PARK-2001;Import"></textarea>
</div>
<button class="btn btn-primary" type="submit"><i class="ti ti-database-import me-1"></i>Import ausfuehren</button>
</form>
</div>
</section>
{% endblock %}

172
templates/index.html Normal file
View File

@@ -0,0 +1,172 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>Uebersicht</h1>
<p>Zentraler Einstieg fuer Bestand, Suche, Bewegungen und Protokolle.</p>
</div>
<div class="grid mb-4">
<section class="card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-search me-1"></i>Suche</h2>
<form method="get" action="{{ url_for('index') }}">
<div class="mb-3">
<label class="form-label">Name, E-Mail oder Kennung</label>
<input class="form-control" type="text" name="q" value="{{ search_query }}" placeholder="z. B. Max oder CHIP-1001">
</div>
<button class="btn btn-primary" type="submit">Suchen</button>
</form>
</div>
</section>
<section class="card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-chart-donut-3 me-1"></i>Bestand</h2>
<div class="stats-grid">
<div class="card stat-card stat-card-users">
<div class="card-body">
<div>
<div class="stat-label">User</div>
<div class="stat-value">{{ stats.users }}</div>
</div>
<div class="stat-icon"><i class="ti ti-users"></i></div>
</div>
</div>
<div class="card stat-card stat-card-chips">
<div class="card-body">
<div>
<div class="stat-label">Tuerchips</div>
<div class="stat-value">{{ stats.active_chips }}</div>
</div>
<div class="stat-icon"><i class="ti ti-key"></i></div>
</div>
</div>
<div class="card stat-card stat-card-cards">
<div class="card-body">
<div>
<div class="stat-label">Parkkarten</div>
<div class="stat-value">{{ stats.active_cards }}</div>
</div>
<div class="stat-icon"><i class="ti ti-credit-card"></i></div>
</div>
</div>
</div>
</div>
</section>
</div>
<section class="card mb-4">
<div class="card-header">
<h2 class="card-title"><i class="ti ti-users me-1"></i>User</h2>
</div>
<div class="table-responsive table-wrap">
{% if users %}
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>Name</th>
<th>E-Mail</th>
<th>Abteilung</th>
<th>Tuerchip</th>
<th>Parkkarte</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.full_name }}</td>
<td>{{ user.email or "-" }}</td>
<td>{{ user.department or "-" }}</td>
<td>
{% if user.chip_code %}
{{ user.chip_code }}<br>
<span class="muted">seit {{ user.chip_assigned_at }}</span>
{% else %}
-
{% endif %}
</td>
<td>
{% if user.parking_card_code %}
{{ user.parking_card_code }}<br>
<span class="muted">seit {{ user.parking_card_assigned_at }}</span>
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body"><p class="mb-0">Keine passenden User gefunden.</p></div>
{% endif %}
</div>
</section>
<section class="card mb-4">
<div class="card-header">
<h2 class="card-title"><i class="ti ti-history me-1"></i>Letzte Bewegungen</h2>
</div>
<div class="table-responsive table-wrap">
{% if transactions %}
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>Zeitpunkt</th>
<th>User</th>
<th>Typ</th>
<th>Kennung</th>
<th>Bearbeiter</th>
<th>Aktion</th>
<th>Beleg</th>
</tr>
</thead>
<tbody>
{% for entry in transactions %}
<tr>
<td>{{ entry.created_at }}</td>
<td>{{ entry.full_name }}</td>
<td>{{ asset_labels[entry.asset_type] }}</td>
<td>{{ entry.asset_code }}</td>
<td>{{ entry.handled_by or "-" }}</td>
<td>{{ action_labels[entry.action] }}</td>
<td><a class="btn btn-sm btn-outline-secondary" href="{{ url_for('print_transaction', transaction_id=entry.id) }}"><i class="ti ti-printer me-1"></i>Druck</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body"><p class="mb-0">Noch keine Bewegungen erfasst.</p></div>
{% endif %}
</div>
</section>
{% if g.current_staff and g.current_staff.role == 'admin' %}
<section class="card">
<div class="card-header">
<h2 class="card-title"><i class="ti ti-file-text me-1"></i>Letzte Logeintraege</h2>
</div>
<div class="table-responsive table-wrap">
{% if recent_logs %}
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>Eintrag</th>
</tr>
</thead>
<tbody>
{% for line in recent_logs %}
<tr>
<td><code>{{ line }}</code></td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="card-body"><p class="mb-0">Noch keine Logeintraege vorhanden.</p></div>
{% endif %}
</div>
</section>
{% endif %}
{% endblock %}

20
templates/login.html Normal file
View File

@@ -0,0 +1,20 @@
{% extends "base.html" %}
{% block content %}
<section class="card card-md login-card">
<div class="card-body">
<h2 class="text-center mb-4"><i class="ti ti-lock me-1"></i>Signage Admin</h2>
<form method="post" action="{{ url_for('login') }}">
<div class="mb-3">
<label class="form-label">Benutzername</label>
<input class="form-control" type="text" name="username" required>
</div>
<div class="mb-3">
<label class="form-label">Passwort</label>
<input class="form-control" type="password" name="password">
</div>
<button class="btn btn-primary w-100" type="submit"><i class="ti ti-login me-1"></i>Login</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,86 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>Bearbeiterverwaltung</h1>
<p>Admins und Bearbeiter verwalten sowie Passwort-Resets ausloesen.</p>
</div>
<div class="content-stack-wide">
<div class="grid">
<article class="card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-user-plus me-1"></i>Bearbeiter anlegen</h2>
<form method="post" action="{{ url_for('manage_staff') }}">
<div class="mb-3">
<label class="form-label">Benutzername</label>
<input class="form-control" type="text" name="username" required>
</div>
<div class="mb-3">
<label class="form-label">Vollstaendiger Name</label>
<input class="form-control" type="text" name="full_name" required>
</div>
<div class="mb-3">
<label class="form-label">Rolle</label>
<select class="form-select" name="role" required>
<option value="staff">Bearbeiter</option>
<option value="admin">Admin</option>
</select>
</div>
<button class="btn btn-primary" type="submit"><i class="ti ti-user-plus me-1"></i>Bearbeiter speichern</button>
</form>
</div>
</article>
<article class="card">
<div class="card-header">
<h2 class="card-title"><i class="ti ti-users-group me-1"></i>Bearbeiterverwaltung</h2>
</div>
{% if staff_users %}
<div class="table-responsive table-wrap">
<table class="table table-vcenter card-table">
<thead>
<tr>
<th>Name</th>
<th>Benutzername</th>
<th>Rolle</th>
<th>Status</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for staff in staff_users %}
<tr>
<td>{{ staff.full_name }}</td>
<td>{{ staff.username }}</td>
<td>{{ staff.role }}</td>
<td>
{% if staff.must_set_password %}
Passwort ausstehend
{% else %}
Aktiv
{% endif %}
</td>
<td>
<div class="d-flex flex-wrap gap-2">
<form method="post" action="{{ url_for('reset_staff_password', staff_id=staff.id) }}">
<button class="btn btn-primary btn-sm" type="submit"><i class="ti ti-refresh me-1"></i>Passwort reset</button>
</form>
<form method="post" action="{{ url_for('delete_staff', staff_id=staff.id) }}" onsubmit="return confirm('Bearbeiter wirklich loeschen?');">
<button class="btn btn-danger btn-sm" type="submit"><i class="ti ti-trash me-1"></i>Loeschen</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="card-body"><p class="mb-0">Keine Bearbeiter vorhanden.</p></div>
{% endif %}
</div>
</article>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,314 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Druckbeleg</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}">
<style>
@page {
size: A4 portrait;
margin: 10mm;
}
:root {
--ccm-bg: #f4f6f8;
--ccm-text: #212121;
--ccm-primary: #da002d;
--ccm-primary-hover: #b00024;
--ccm-header: #2b2f36;
--ccm-surface: #ffffff;
--ccm-border: #d9dee3;
--ccm-muted: #6b7280;
}
body {
margin: 0;
font-family: "Segoe UI", Arial, sans-serif;
color: var(--ccm-text);
background: var(--ccm-bg);
}
.page {
max-width: 960px;
margin: 0 auto;
padding: 2rem;
}
.sheet {
background: var(--ccm-surface);
border: 1px solid var(--ccm-border);
border-radius: 1rem;
box-shadow: 0 12px 30px rgba(43, 47, 54, 0.08);
overflow: hidden;
}
.topbar {
background: linear-gradient(135deg, #da002d 0%, #b00024 100%);
padding: 1rem 1.5rem;
}
.logo-row {
display: flex;
justify-content: flex-start;
align-items: center;
padding: 1.25rem 1.5rem 0;
background: linear-gradient(135deg, #da002d 0%, #b00024 100%);
}
.header {
display: flex;
justify-content: flex-start;
align-items: flex-start;
background: linear-gradient(135deg, #da002d 0%, #b00024 100%);
color: #ffffff;
padding: 1.5rem;
text-align: left;
}
.header-content {
text-align: left;
}
.header img {
height: 28px;
width: auto;
}
.print-logo {
filter: none;
}
h1 {
margin: 0;
font-size: 1.8rem;
}
.subtitle {
margin-top: 0.35rem;
color: rgba(255, 255, 255, 0.9);
}
.content {
padding: 1.5rem;
}
.details {
width: 100%;
border-collapse: collapse;
}
.details th,
.details td {
text-align: left;
padding: 0.75rem 0;
border-bottom: 1px solid var(--ccm-border);
vertical-align: top;
}
.details th {
width: 30%;
color: var(--ccm-muted);
font-weight: 600;
}
.note-box {
margin-top: 1.5rem;
padding: 1rem 1.25rem;
border-left: 4px solid var(--ccm-primary);
background: rgba(218, 0, 45, 0.06);
border-radius: 0.75rem;
}
.signature-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 2rem;
margin-top: 4rem;
}
.signature-box {
min-height: 120px;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.signature-line {
border-top: 1px solid var(--ccm-text);
padding-top: 0.75rem;
font-weight: 600;
}
.signature-date {
margin-top: 0.5rem;
color: var(--ccm-muted);
font-size: 0.95rem;
}
.print-actions {
display: flex;
gap: 0.75rem;
margin-top: 2rem;
}
.button {
display: inline-block;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
text-decoration: none;
font-weight: 600;
}
.button-primary {
background: var(--ccm-primary);
color: #ffffff;
}
.button-secondary {
background: var(--ccm-bg);
color: var(--ccm-text);
}
@media print {
html,
body {
width: 210mm;
min-height: 297mm;
background: #ffffff;
}
body {
display: flex;
justify-content: center;
}
.print-actions {
display: none;
}
.page {
width: 100%;
max-width: 190mm;
padding: 0;
margin: 0;
}
.sheet {
border: 0;
border-radius: 0;
box-shadow: none;
width: 100%;
min-height: 277mm;
}
.print-logo {
filter: brightness(0) saturate(100%);
}
.topbar {
padding: 0 8mm 8mm;
}
.logo-row {
padding: 0 8mm 6mm;
}
.header {
padding: 8mm;
}
.content {
padding: 8mm;
}
.signature-grid {
margin-top: 26mm;
}
}
@media (max-width: 700px) {
.page {
padding: 1rem;
}
.header {
flex-direction: column;
align-items: flex-start;
}
.signature-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<main class="page">
<section class="sheet">
<div class="topbar"></div>
<div class="logo-row">
<img class="print-logo" src="{{ url_for('static', filename='cancom.svg') }}" alt="CANCOM Logo">
</div>
<div class="header">
<div class="header-content">
<h1>{{ action_labels[transaction.action] }} {{ asset_labels[transaction.asset_type] }}</h1>
<div class="subtitle">{{ print_descriptions[transaction.action] }}</div>
</div>
</div>
<div class="content">
<table class="details">
<tr>
<th>Datum</th>
<td>{{ transaction.created_at }}</td>
</tr>
<tr>
<th>Anwender</th>
<td>{{ transaction.full_name }}</td>
</tr>
<tr>
<th>E-Mail</th>
<td>{{ transaction.email or "-" }}</td>
</tr>
<tr>
<th>Abteilung</th>
<td>{{ transaction.department or "-" }}</td>
</tr>
<tr>
<th>Medium</th>
<td>{{ asset_labels[transaction.asset_type] }}</td>
</tr>
<tr>
<th>Kennung</th>
<td>{{ transaction.asset_code }}</td>
</tr>
<tr>
<th>Bearbeiter</th>
<td>{{ transaction.handled_by or "-" }}</td>
</tr>
</table>
<div class="note-box">
Mit den untenstehenden Unterschriften bestaetigen Bearbeiter und Anwender die korrekte {{ action_labels[transaction.action] | lower }} des angegebenen Mediums.
</div>
<div class="signature-grid">
<div class="signature-box">
<div class="signature-line">{{ transaction.handled_by or "Bearbeiter" }}</div>
<div class="signature-date">Datum: {{ transaction.created_at }}</div>
</div>
<div class="signature-box">
<div class="signature-line">{{ transaction.full_name }}</div>
<div class="signature-date">Datum: {{ transaction.created_at }}</div>
</div>
</div>
</div>
</section>
<div class="print-actions">
<a class="button button-primary" href="#" onclick="window.print(); return false;">Drucken</a>
<a class="button button-secondary" href="{{ url_for('index') }}">Zur Uebersicht</a>
</div>
</main>
</body>
</html>

View File

@@ -0,0 +1,33 @@
{% extends "base.html" %}
{% block content %}
<div class="page-section-title">
<h1>Rueckgabe</h1>
<p>Ausgegebene Tuerchips und Parkkarten wieder zuruecknehmen.</p>
</div>
<section class="card form-card">
<div class="card-body">
<h2 class="card-title"><i class="ti ti-arrow-back-up me-1"></i>Chip oder Parkkarte zuruecknehmen</h2>
<form method="post" action="{{ url_for('return_asset') }}">
<div class="mb-3">
<label class="form-label">User</label>
<select class="form-select" name="user_id" required>
<option value="">Bitte waehlen</option>
{% for user in users %}
<option value="{{ user.id }}">{{ user.full_name }}</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label class="form-label">Typ</label>
<select class="form-select" name="asset_type" required>
<option value="chip">Tuerchip</option>
<option value="parking_card">Parkkarte</option>
</select>
</div>
<button class="btn btn-primary" type="submit"><i class="ti ti-check me-1"></i>Rueckgabe speichern</button>
</form>
</div>
</section>
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block content %}
<div class="row justify-content-center">
<div class="col-sm-8 col-md-6 col-lg-4">
<section class="card card-md mt-4">
<div class="card-body">
<h2 class="text-center mb-4"><i class="ti ti-keyboard me-1"></i>Passwort vergeben</h2>
<form method="post" action="{{ url_for('set_password') }}">
<div class="mb-3">
<label class="form-label">Neues Passwort</label>
<input class="form-control" type="password" name="password" required>
</div>
<div class="mb-3">
<label class="form-label">Passwort wiederholen</label>
<input class="form-control" type="password" name="password_confirm" required>
</div>
<button class="btn btn-primary w-100" type="submit">Passwort speichern</button>
</form>
</div>
</section>
</div>
</div>
{% endblock %}