Darstellung von Umlauten

This commit is contained in:
Erik Thiele
2026-05-19 22:02:49 +02:00
parent 0199a21a66
commit 68e0ea7658
11 changed files with 60 additions and 28 deletions

View File

@@ -1,4 +1,4 @@
# Verwaltung fuer Tuerchips, Parkkarten und Poolfahrzeuge # keyadmin
## Voraussetzungen ## Voraussetzungen
- Python 3.10 oder neuer - Python 3.10 oder neuer

2
app.py
View File

@@ -12,7 +12,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
app = Flask(__name__) app = Flask(__name__)
app.config["SECRET_KEY"] = "dev-secret-key" app.config["SECRET_KEY"] = "dev-secret-key"
APP_VERSION = "1.1.0" APP_VERSION = "1.5.0"
DATABASE = Path(__file__).with_name("inventory.db") DATABASE = Path(__file__).with_name("inventory.db")
LOGFILE = Path(__file__).with_name("inventory.log") LOGFILE = Path(__file__).with_name("inventory.log")

View File

@@ -996,3 +996,35 @@
2026-05-19 20:21:17,850 INFO 127.0.0.1 - - [19/May/2026 20:21:17] "GET /static/cancom.svg HTTP/1.1" 304 - 2026-05-19 20:21:17,850 INFO 127.0.0.1 - - [19/May/2026 20:21:17] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 20:21:44,466 INFO 127.0.0.1 - - [19/May/2026 20:21:44] "GET / HTTP/1.1" 200 - 2026-05-19 20:21:44,466 INFO 127.0.0.1 - - [19/May/2026 20:21:44] "GET / HTTP/1.1" 200 -
2026-05-19 20:21:44,485 INFO 127.0.0.1 - - [19/May/2026 20:21:44] "GET /static/cancom.svg HTTP/1.1" 304 - 2026-05-19 20:21:44,485 INFO 127.0.0.1 - - [19/May/2026 20:21:44] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 21:56:36,764 INFO WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5006
* Running on http://192.168.10.191:5006
2026-05-19 21:56:36,767 INFO Press CTRL+C to quit
2026-05-19 21:56:36,771 INFO * Restarting with stat
2026-05-19 21:56:36,914 WARNING * Debugger is active!
2026-05-19 21:56:36,931 INFO * Debugger PIN: 428-899-358
2026-05-19 21:56:45,615 INFO 127.0.0.1 - - [19/May/2026 21:56:45] "GET / HTTP/1.1" 200 -
2026-05-19 21:56:45,708 INFO 127.0.0.1 - - [19/May/2026 21:56:45] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 21:59:40,187 INFO 127.0.0.1 - - [19/May/2026 21:59:40] "GET / HTTP/1.1" 200 -
2026-05-19 21:59:40,223 INFO 127.0.0.1 - - [19/May/2026 21:59:40] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 21:59:46,979 INFO 127.0.0.1 - - [19/May/2026 21:59:46] "GET / HTTP/1.1" 200 -
2026-05-19 21:59:47,003 INFO 127.0.0.1 - - [19/May/2026 21:59:47] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 21:59:48,331 INFO 127.0.0.1 - - [19/May/2026 21:59:48] "GET /users/new HTTP/1.1" 200 -
2026-05-19 21:59:48,356 INFO 127.0.0.1 - - [19/May/2026 21:59:48] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 21:59:49,276 INFO 127.0.0.1 - - [19/May/2026 21:59:49] "GET / HTTP/1.1" 200 -
2026-05-19 21:59:49,296 INFO 127.0.0.1 - - [19/May/2026 21:59:49] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:00:38,267 INFO 127.0.0.1 - - [19/May/2026 22:00:38] "GET /assign HTTP/1.1" 200 -
2026-05-19 22:00:38,296 INFO 127.0.0.1 - - [19/May/2026 22:00:38] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:01:04,764 INFO 127.0.0.1 - - [19/May/2026 22:01:04] "GET /return HTTP/1.1" 200 -
2026-05-19 22:01:04,787 INFO 127.0.0.1 - - [19/May/2026 22:01:04] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:01:13,231 INFO 127.0.0.1 - - [19/May/2026 22:01:13] "GET / HTTP/1.1" 200 -
2026-05-19 22:01:13,263 INFO 127.0.0.1 - - [19/May/2026 22:01:13] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:01:16,960 INFO 127.0.0.1 - - [19/May/2026 22:01:16] "GET /return HTTP/1.1" 200 -
2026-05-19 22:01:16,984 INFO 127.0.0.1 - - [19/May/2026 22:01:16] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:01:31,181 INFO 127.0.0.1 - - [19/May/2026 22:01:31] "GET / HTTP/1.1" 200 -
2026-05-19 22:01:31,202 INFO 127.0.0.1 - - [19/May/2026 22:01:31] "GET /static/cancom.svg HTTP/1.1" 304 -
2026-05-19 22:02:24,449 INFO * Detected change in '/Users/erik/Documents/DEV/Key Verwaltung/app.py', reloading
2026-05-19 22:02:24,476 INFO * Restarting with stat
2026-05-19 22:02:25,947 WARNING * Debugger is active!
2026-05-19 22:02:25,994 INFO * Debugger PIN: 428-899-358

View File

@@ -22,7 +22,7 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Typ</label> <label class="form-label">Typ</label>
<select class="form-select" name="asset_type" id="asset_type" required> <select class="form-select" name="asset_type" id="asset_type" required>
<option value="chip" {% if selected_asset_type == 'chip' %}selected{% endif %}>Tuerchip</option> <option value="chip" {% if selected_asset_type == 'chip' %}selected{% endif %}>Türchip</option>
<option value="parking_card" {% if selected_asset_type == 'parking_card' %}selected{% endif %}>Parkkarte</option> <option value="parking_card" {% if selected_asset_type == 'parking_card' %}selected{% endif %}>Parkkarte</option>
<option value="pool_vehicle" {% if selected_asset_type == 'pool_vehicle' %}selected{% endif %}>Poolfahrzeug</option> <option value="pool_vehicle" {% if selected_asset_type == 'pool_vehicle' %}selected{% endif %}>Poolfahrzeug</option>
</select> </select>

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title or "Chipverwaltung" }}</title> <title>{{ title or "keyadmin" }}</title>
<link rel="icon" href="{{ url_for('static', filename='favicon.ico') }}"> <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://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"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
@@ -472,7 +472,7 @@
{% for category, message in messages %} {% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible" role="alert"> <div class="alert alert-{{ category }} alert-dismissible" role="alert">
{% if category == 'import-errors' %} {% if category == 'import-errors' %}
<strong>Import konnte nicht ausgefuehrt werden.</strong> <strong>Import konnte nicht ausgeführt werden.</strong>
<ul class="flash-list mb-0"> <ul class="flash-list mb-0">
{% for entry in message %} {% for entry in message %}
<li>{{ entry }}</li> <li>{{ entry }}</li>
@@ -481,7 +481,7 @@
{% else %} {% else %}
{{ message }} {{ message }}
{% endif %} {% endif %}
<button type="button" class="btn-close" aria-label="Schliessen" onclick="this.parentElement.remove();"></button> <button type="button" class="btn-close" aria-label="Schließen" onclick="this.parentElement.remove();"></button>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@@ -493,8 +493,8 @@
<div class="navbar-brand navbar-brand-autodark pe-0 pe-md-3"> <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"> <img class="navbar-brand-logo me-3" src="{{ url_for('static', filename='cancom.svg') }}" alt="CANCOM Logo">
<span class="navbar-brand-wordmark"> <span class="navbar-brand-wordmark">
<strong>CANCOM BU Sued/West</strong> <strong>keyadmin</strong>
<span>Tuerchip-, Parkkarten- und Poolfahrzeugverwaltung</span> <span>Zugangsverwaltung</span>
</span> </span>
</div> </div>
<div class="navbar-nav flex-row order-md-last top-actions"> <div class="navbar-nav flex-row order-md-last top-actions">
@@ -514,10 +514,10 @@
<div class="navbar navbar-dark nav-surface"> <div class="navbar navbar-dark nav-surface">
<div class="container-xl"> <div class="container-xl">
<div class="d-flex flex-wrap gap-2 py-2"> <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 == 'index' %}active{% endif %}" href="{{ url_for('index') }}"><i class="ti ti-layout-dashboard me-1"></i>Übersicht</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 == '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 == '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> <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>Rückgabe</a>
{% if g.current_staff.role == 'admin' %} {% 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 == '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> <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>

View File

@@ -3,7 +3,7 @@
{% block content %} {% block content %}
<div class="page-section-title"> <div class="page-section-title">
<h1>User anlegen</h1> <h1>User anlegen</h1>
<p>Neue Anwender fuer die Verwaltung von Tuerchips und Parkkarten erfassen.</p> <p>Neue Anwender für die Verwaltung von Türchips und Parkkarten erfassen.</p>
</div> </div>
<section class="card form-card"> <section class="card form-card">
@@ -11,7 +11,7 @@
<h2 class="card-title"><i class="ti ti-user-plus me-1"></i>Neuen User anlegen</h2> <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') }}"> <form method="post" action="{{ url_for('create_user') }}">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Vollstaendiger Name</label> <label class="form-label">Vollständiger Name</label>
<input class="form-control" type="text" name="full_name" required> <input class="form-control" type="text" name="full_name" required>
</div> </div>
<div class="mb-3"> <div class="mb-3">

View File

@@ -3,13 +3,13 @@
{% block content %} {% block content %}
<div class="page-section-title"> <div class="page-section-title">
<h1>Import</h1> <h1>Import</h1>
<p>Vorhandene Bestandsdaten zeilenweise in die Datenbank uebernehmen.</p> <p>Vorhandene Bestandsdaten zeilenweise in die Datenbank übernehmen.</p>
</div> </div>
<section class="card form-card"> <section class="card form-card">
<div class="card-body"> <div class="card-body">
<h2 class="card-title"><i class="ti ti-file-import me-1"></i>Importdaten einfuegen</h2> <h2 class="card-title"><i class="ti ti-file-import me-1"></i>Importdaten einfügen</h2>
<p class="muted">CSV-Format pro Zeile: <code>User;Typ;Kennung;Aktion</code>. Fuer die Aktion ist <code>Import</code> vorgesehen.</p> <p class="muted">CSV-Format pro Zeile: <code>User;Typ;Kennung;Aktion</code>. Für die Aktion ist <code>Import</code> vorgesehen.</p>
<form method="post" action="{{ url_for('import_data') }}" enctype="multipart/form-data"> <form method="post" action="{{ url_for('import_data') }}" enctype="multipart/form-data">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">CSV-Datei</label> <label class="form-label">CSV-Datei</label>
@@ -17,7 +17,7 @@
<div class="form-hint">UTF-8, Semikolon-getrennt. Wenn eine Datei ausgewaehlt ist, wird sie bevorzugt importiert.</div> <div class="form-hint">UTF-8, Semikolon-getrennt. Wenn eine Datei ausgewaehlt ist, wird sie bevorzugt importiert.</div>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Importzeilen alternativ direkt einfuegen</label> <label class="form-label">Importzeilen alternativ direkt einfügen</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> <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> </div>
<button class="btn btn-primary" type="submit"><i class="ti ti-database-import me-1"></i>Import ausfuehren</button> <button class="btn btn-primary" type="submit"><i class="ti ti-database-import me-1"></i>Import ausfuehren</button>

View File

@@ -2,8 +2,8 @@
{% block content %} {% block content %}
<div class="page-section-title"> <div class="page-section-title">
<h1>Uebersicht</h1> <h1>Übersicht</h1>
<p>Zentraler Einstieg fuer Bestand, Suche, Bewegungen und Protokolle.</p> <p>Zentraler Einstieg für Bestand, Suche, Bewegungen und Protokolle.</p>
</div> </div>
<div class="grid mb-4"> <div class="grid mb-4">
@@ -36,7 +36,7 @@
<div class="card stat-card stat-card-chips"> <div class="card stat-card stat-card-chips">
<div class="card-body"> <div class="card-body">
<div> <div>
<div class="stat-label">Tuerchips</div> <div class="stat-label">Türchips</div>
<div class="stat-value">{{ stats.active_chips }}</div> <div class="stat-value">{{ stats.active_chips }}</div>
</div> </div>
<div class="stat-icon"><i class="ti ti-key"></i></div> <div class="stat-icon"><i class="ti ti-key"></i></div>
@@ -77,7 +77,7 @@
<th>Name</th> <th>Name</th>
<th>E-Mail</th> <th>E-Mail</th>
<th>Abteilung</th> <th>Abteilung</th>
<th>Tuerchip</th> <th>Türchip</th>
<th>Parkkarte</th> <th>Parkkarte</th>
<th>Poolfahrzeug</th> <th>Poolfahrzeug</th>
</tr> </tr>

View File

@@ -3,7 +3,7 @@
{% block content %} {% block content %}
<section class="card card-md login-card"> <section class="card card-md login-card">
<div class="card-body"> <div class="card-body">
<h2 class="text-center mb-4"><i class="ti ti-lock me-1"></i>Chipverwaltung</h2> <h2 class="text-center mb-4"><i class="ti ti-lock me-1"></i>keyadmin</h2>
<form method="post" action="{{ url_for('login') }}"> <form method="post" action="{{ url_for('login') }}">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Benutzername</label> <label class="form-label">Benutzername</label>

View File

@@ -289,7 +289,7 @@
</table> </table>
<div class="note-box"> <div class="note-box">
Mit den untenstehenden Unterschriften bestaetigen Bearbeiter und Anwender die korrekte {{ action_labels[transaction.action] | lower }} des angegebenen Mediums. Mit den untenstehenden Unterschriften bestätigen Bearbeiter und Anwender die korrekte {{ action_labels[transaction.action] | lower }} des angegebenen Mediums.
</div> </div>
<div class="signature-grid"> <div class="signature-grid">
@@ -307,7 +307,7 @@
<div class="print-actions"> <div class="print-actions">
<a class="button button-primary" href="#" onclick="window.print(); return false;">Drucken</a> <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> <a class="button button-secondary" href="{{ url_for('index') }}">Zur Übersicht</a>
</div> </div>
</main> </main>
</body> </body>

View File

@@ -2,13 +2,13 @@
{% block content %} {% block content %}
<div class="page-section-title"> <div class="page-section-title">
<h1>Rueckgabe</h1> <h1>Rückgabe</h1>
<p>Ausgegebene Tuerchips, Parkkarten und Poolfahrzeuge wieder zuruecknehmen.</p> <p>Ausgegebene Türchips, Parkkarten und Poolfahrzeuge wieder zurücknehmen.</p>
</div> </div>
<section class="card form-card"> <section class="card form-card">
<div class="card-body"> <div class="card-body">
<h2 class="card-title"><i class="ti ti-arrow-back-up me-1"></i>Medium zuruecknehmen</h2> <h2 class="card-title"><i class="ti ti-arrow-back-up me-1"></i>Medium zurücknehmen</h2>
<form method="post" action="{{ url_for('return_asset') }}"> <form method="post" action="{{ url_for('return_asset') }}">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">User</label> <label class="form-label">User</label>
@@ -22,12 +22,12 @@
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Typ</label> <label class="form-label">Typ</label>
<select class="form-select" name="asset_type" id="asset_type" required> <select class="form-select" name="asset_type" id="asset_type" required>
<option value="chip" {% if selected_asset_type == 'chip' %}selected{% endif %}>Tuerchip</option> <option value="chip" {% if selected_asset_type == 'chip' %}selected{% endif %}>Türchip</option>
<option value="parking_card" {% if selected_asset_type == 'parking_card' %}selected{% endif %}>Parkkarte</option> <option value="parking_card" {% if selected_asset_type == 'parking_card' %}selected{% endif %}>Parkkarte</option>
<option value="pool_vehicle" {% if selected_asset_type == 'pool_vehicle' %}selected{% endif %}>Poolfahrzeug</option> <option value="pool_vehicle" {% if selected_asset_type == 'pool_vehicle' %}selected{% endif %}>Poolfahrzeug</option>
</select> </select>
</div> </div>
<button class="btn btn-primary" type="submit"><i class="ti ti-check me-1"></i>Rueckgabe speichern</button> <button class="btn btn-primary" type="submit"><i class="ti ti-check me-1"></i>Rückgabe speichern</button>
</form> </form>
</div> </div>
</section> </section>