Refactor license management and remove unused features

- Removed the JWT authentication setup and related access token handling.
- Deleted the test license activation functionality and associated routes.
- Removed the admin license management interface and related templates.
- Cleaned up the appointments page by removing test license options.
- Updated various templates to reflect changes in terminology from "license" to "instance".
- Removed the verify module and its associated functions for license key management.
- Deleted unused templates related to license management.
- Added a new empty JSON file for licenses backup.
This commit is contained in:
2026-04-18 20:21:33 +02:00
parent d9b812812f
commit 280d25dda9
15 changed files with 45 additions and 1008 deletions
-72
View File
@@ -1,72 +0,0 @@
{% extends "base.html" %}
{% block title %}Admin | Lizenzverwaltung{% endblock %}
{% block content %}
<section class="panel">
<h1>Lizenzverwaltung</h1>
<p>Lizenzen für Nutzer anlegen, bearbeiten und löschen.</p>
</section>
<section class="layout">
<form method="post" class="form-card">
<h3>Neue Lizenz</h3>
<input type="hidden" name="action" value="create">
<select name="username" required>
<option value="">Benutzer auswählen</option>
{% for user in users %}
<option value="{{ user.username }}">{{ user.username }} ({{ user.display_name }})</option>
{% endfor %}
</select>
<input type="text" name="school_name" placeholder="Schulname" required>
<input type="text" name="license_key" placeholder="Lizenz-Key (optional, eigener Wert möglich)">
<input type="text" name="plan" placeholder="Plan (z. B. Standard)">
<input type="text" name="status" placeholder="Status (Aktiv, Pausiert)">
<input type="text" name="valid_until" placeholder="Gültig bis (YYYY-MM-DD)">
<button type="submit">Lizenz anlegen</button>
</form>
<div class="list">
{% for item in licenses %}
<article class="entry">
<h3>{{ item.username }} - {{ item.school_name }}</h3>
<p><strong>Key:</strong> {{ item.license_key }} | <strong>Plan:</strong> {{ item.plan }}</p>
<p><strong>Status:</strong> {{ item.status }} | <strong>Gültig bis:</strong> {{ item.valid_until }}</p>
{% if item.transferred_at %}
<p><strong>Weitergegeben:</strong> {{ item.transferred_at }}</p>
{% endif %}
<form method="post" class="inline-form">
<input type="hidden" name="action" value="update">
<input type="hidden" name="license_id" value="{{ item.id }}">
<input type="text" name="school_name" value="{{ item.school_name }}" placeholder="Schulname">
<input type="text" name="license_key" value="{{ item.license_key }}" placeholder="Lizenz-Key">
<input type="text" name="plan" value="{{ item.plan }}" placeholder="Plan">
<input type="text" name="status" value="{{ item.status }}" placeholder="Status">
<input type="text" name="valid_until" value="{{ item.valid_until }}" placeholder="Gültig bis">
<button type="submit">Aktualisieren</button>
</form>
<form method="post">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="license_id" value="{{ item.id }}">
<button type="submit" class="danger">Löschen</button>
</form>
</article>
{% else %}
<article class="entry"><p>Noch keine Lizenzen vorhanden.</p></article>
{% endfor %}
</div>
</section>
<style>
.panel { margin-bottom: 1rem; }
.layout { display: grid; grid-template-columns: 320px 1fr; gap: 1rem; }
.form-card, .entry { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.form-card { display: grid; gap: 0.6rem; align-self: start; }
.list { display: grid; gap: 0.8rem; }
.inline-form { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.45rem; margin-top: 0.6rem; }
input, select { width: 100%; border: 1px solid #c9d8e3; border-radius: 10px; padding: 0.52rem; font: inherit; background: #fff; }
button { border: 1px solid #0a4c74; background: linear-gradient(120deg, #0c5a86 0%, #08486c 100%); color: #fff; padding: 0.45rem 0.8rem; border-radius: 999px; font-weight: 700; }
button.danger { border-color: #d04a49; background: #fff; color: #922e2e; margin-top: 0.55rem; }
@media (max-width: 980px) { .layout { grid-template-columns: 1fr; } .inline-form { grid-template-columns: 1fr; } }
</style>
{% endblock %}
-9
View File
@@ -123,15 +123,6 @@
</form>
<a class="action-link" href="{{ url_for('admin_download_core_logs') }}">Core-Logs herunterladen</a>
</div>
<h2 style="margin-top: 1rem;">Backup-Export / Einspielen</h2>
<div class="actions">
<a class="action-link" href="{{ url_for('download_backup') }}">Lizenz-Backup exportieren</a>
<form method="post" action="{{ url_for('upload_backup') }}" enctype="multipart/form-data" class="inline-restore-form">
<input type="file" name="file" accept=".json,application/json" required>
<button type="submit">Lizenz-Backup einspielen</button>
</form>
</div>
</section>
<section class="panel logs-panel">
+8 -23
View File
@@ -103,7 +103,7 @@
{% block content %}
<section class="packages-hero">
<h1>Buchungsoptionen als Softwarepakete</h1>
<p>Alle Pakete sind frei einsehbar, auch ohne Admin. Die Testversion kann nur mit aktivem Konto gestartet werden.</p>
<p>Alle Pakete sind frei einsehbar, auch ohne Admin.</p>
<p>Jedes Paket basiert auf derselben Sicherheitsarchitektur mit Rollenrechten, geschützten Sessions und nachvollziehbaren Aktionen.</p>
</section>
@@ -119,23 +119,12 @@
</ul>
<div class="package-actions">
{% if 'username' in session %}
{% if package.slug == 'test' %}
<form method="POST" action="{{ url_for('start_test_package') }}">
<input type="hidden" name="package" value="{{ package.slug }}">
<button class="btn" type="submit">Testversion starten</button>
</form>
{% else %}
<form method="POST" action="{{ url_for('book_option_package') }}">
<input type="hidden" name="package" value="{{ package.slug }}">
<button class="btn" type="submit">Option buchen</button>
</form>
{% endif %}
<form method="POST" action="{{ url_for('book_option_package') }}">
<input type="hidden" name="package" value="{{ package.slug }}">
<button class="btn" type="submit">Option buchen</button>
</form>
{% else %}
{% if package.slug == 'test' %}
<a class="btn secondary" href="{{ url_for('login') }}">Mit Konto testen</a>
{% else %}
<a class="btn secondary" href="{{ url_for('login') }}">Mit Konto buchen</a>
{% endif %}
<a class="btn secondary" href="{{ url_for('login') }}">Mit Konto buchen</a>
{% endif %}
</div>
</article>
@@ -144,13 +133,9 @@
<section class="notice">
{% if 'username' in session %}
{% if active_test_license %}
<p><strong>Aktive Testversion:</strong> {{ active_test_license.plan }} | Key: {{ active_test_license.license_key }} | Gültig bis: {{ active_test_license.valid_until }}</p>
{% else %}
<p><strong>Hinweis:</strong> Für Ihr Konto ist noch keine Testversion aktiv. Starten Sie oben die Testversion beim Paket Testversion.</p>
{% endif %}
<p><strong>Hinweis:</strong> Buchungsanfragen werden direkt vom Admin-Team verarbeitet und Ihrer Instanz zugeordnet.</p>
{% else %}
<p><strong>Hinweis:</strong> Bitte einloggen oder registrieren, um die Testversion zu starten.</p>
<p><strong>Hinweis:</strong> Bitte einloggen oder registrieren, um Buchungsoptionen anzufragen.</p>
{% endif %}
</section>
{% endblock %}
-3
View File
@@ -383,7 +383,6 @@
<details class="nav-dropdown">
<summary class="nav-btn">Nutzerbereich</summary>
<div class="dropdown-menu">
<a href="{{ url_for('my_licenses') }}">Meine Lizenzen</a>
<a href="{{ url_for('my_invoices') }}">Meine Rechnungen</a>
<a href="{{ url_for('my_instance_management') }}">Meine Instanz</a>
<a href="{{ url_for('user_chat') }}">Chat mit Admin</a>
@@ -397,8 +396,6 @@
<div class="dropdown-menu">
<a href="{{ url_for('admin_dashboard') }}">Dashboard</a>
<a href="{{ url_for('admin_users') }}">Userverwaltung</a>
<a href="{{ url_for('admin_licenses') }}">Lizenzverwaltung</a>
<a href="{{ url_for('admin_license_keys') }}">Lizenz-Key Verwaltung</a>
<a href="{{ url_for('admin_invoices') }}">Rechnungsverwaltung</a>
<a href="{{ url_for('admin_chats') }}">Admin-Chat</a>
<a href="{{ url_for('admin_tickets') }}">Support-Center</a>
+2 -2
View File
@@ -14,8 +14,8 @@
<p>Alle Geräte, Räume und Zuständigkeiten an einem Ort. Das verkürzt Suchzeiten und reduziert Rückfragen.</p>
</article>
<article class="card">
<h2>Lizenz- und Fristenkontrolle</h2>
<p>Lizenzlaufzeiten werden transparent angezeigt, inklusive Hinweise auf bald ablaufende Nutzungen.</p>
<h2>Instanz- und Fristenkontrolle</h2>
<p>Instanzzustände und wichtige Wartungsfristen werden transparent angezeigt, inklusive klarer Hinweise für den Betrieb.</p>
</article>
<article class="card">
<h2>Support mit klaren Prioritäten</h2>
+1 -1
View File
@@ -5,7 +5,7 @@
{% block content %}
<section class="page-hero">
<h1>Inventarsystem für Schulen</h1>
<p>Alle wichtigen Funktionen für den Schulalltag in einer klaren Oberfläche: von Inventar über Lizenzen bis zur nachvollziehbaren Historie.</p>
<p>Alle wichtigen Funktionen für den Schulalltag in einer klaren Oberfläche: von Inventar bis zur nachvollziehbaren Historie.</p>
</section>
<section class="split">
-268
View File
@@ -1,268 +0,0 @@
{% extends "base.html" %}
{% block title %}Admin | Lizenz-Key Verwaltung{% endblock %}
{% block head %}
{{ super() }}
<style>
.key-header {
margin-bottom: 1rem;
}
.key-header p {
margin-top: 0.4rem;
max-width: 70ch;
}
.key-layout {
display: grid;
grid-template-columns: 360px 1fr;
gap: 1rem;
}
.key-card {
background: #ffffff;
border: 1px solid #d4e0e9;
border-radius: 14px;
padding: 1rem;
box-shadow: 0 10px 28px rgba(20, 40, 55, 0.05);
}
.key-card h3 {
font-size: 1.02rem;
color: #143a55;
margin-bottom: 0.65rem;
}
.key-form {
display: grid;
gap: 0.55rem;
}
.key-form label {
font-size: 0.86rem;
font-weight: 700;
color: #3f5f74;
}
.key-form select,
.key-form input,
.key-form button {
width: 100%;
font: inherit;
border-radius: 10px;
padding: 0.52rem 0.62rem;
}
.key-form select,
.key-form input {
border: 1px solid #c7d7e2;
background: #ffffff;
color: #17394f;
}
.btn-main,
.btn-alt,
.btn-danger {
border: none;
cursor: pointer;
font-weight: 700;
}
.btn-main {
color: #ffffff;
background: linear-gradient(120deg, #0b5b89 0%, #0b4262 100%);
}
.btn-alt {
color: #0f3d59;
border: 1px solid #bfd2df;
background: #f8fbfd;
}
.btn-danger {
color: #7e2d2d;
border: 1px solid #e8b8b8;
background: #fff3f3;
}
.key-actions {
display: grid;
gap: 0.5rem;
}
.key-actions a {
text-align: center;
}
.key-table-wrap {
overflow-x: auto;
}
.key-table {
width: 100%;
border-collapse: collapse;
}
.key-table th,
.key-table td {
border-bottom: 1px solid #e0e8ef;
text-align: left;
padding: 0.68rem 0.5rem;
vertical-align: middle;
}
.key-table th {
font-size: 0.82rem;
color: #4b697d;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.key-table td {
color: #17384f;
font-size: 0.95rem;
}
.code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.85rem;
background: #f2f6fa;
border: 1px solid #d9e4ed;
border-radius: 8px;
padding: 0.2rem 0.35rem;
word-break: break-all;
}
.badge {
display: inline-block;
border-radius: 999px;
padding: 0.2rem 0.5rem;
font-size: 0.78rem;
font-weight: 700;
}
.badge.assigned {
border: 1px solid #9fd2bf;
color: #14684a;
background: #edfaf4;
}
.badge.unassigned {
border: 1px solid #ebd08c;
color: #8a6113;
background: #fff8e8;
}
.remove-form {
display: inline;
}
@media (max-width: 1024px) {
.key-layout {
grid-template-columns: 1fr;
}
}
</style>
{% endblock %}
{% block content %}
<section class="key-header">
<h1>Lizenz-Key Verwaltung</h1>
<p>Generieren, zuweisen und sichern Sie Lizenz-Keys im gleichen Stil wie der restliche Admin-Bereich.</p>
</section>
<section class="key-layout">
<aside class="key-card">
<h3>Key erstellen</h3>
<form method="POST" action="{{ url_for('generate_new') }}" class="key-form">
<button type="submit" class="btn-main">Neuen Key generieren</button>
</form>
<h3 style="margin-top: 1rem;">Key zuweisen</h3>
<form method="POST" action="{{ url_for('allocate_key') }}" class="key-form">
<label for="alloc_user">Benutzer</label>
<select id="alloc_user" name="username" required>
<option value="">Benutzer auswählen</option>
{% for user in users %}
<option value="{{ user.username }}">{{ user.username }} ({{ user.display_name }})</option>
{% endfor %}
</select>
<label for="alloc_key">Lizenz-Key</label>
<select id="alloc_key" name="license_key" required>
<option value="">Key auswählen</option>
{% for item in keys %}
<option value="{{ item.license_key }}">{{ item.user_id or '----' }} | {{ item.license_key[:22] }}{% if item.license_key|length > 22 %}...{% endif %}</option>
{% endfor %}
</select>
<button type="submit" class="btn-main">Key zuweisen</button>
</form>
<h3 style="margin-top: 1rem;">Backup</h3>
<div class="key-actions">
<a href="{{ url_for('download_backup') }}" class="btn-alt">Backup herunterladen</a>
<form method="POST" action="{{ url_for('upload_backup') }}" enctype="multipart/form-data" class="key-form">
<input type="file" name="file" accept=".json" required>
<button type="submit" class="btn-danger" onclick="return confirm('Backup wirklich einspielen? Aktuelle Keys werden ersetzt.');">Backup einspielen</button>
</form>
</div>
</aside>
<article class="key-card">
<h3>Alle Keys</h3>
<div class="key-table-wrap">
<table class="key-table">
<thead>
<tr>
<th>User ID</th>
<th>Lizenz-Key</th>
<th>Benutzer</th>
<th>HWID</th>
<th>Status</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for item in keys %}
<tr>
<td>{{ item.user_id or '-' }}</td>
<td><span class="code">{{ item.license_key }}</span></td>
<td>{{ item.username or '-' }}</td>
<td>
{% if item.hwid_uuid %}
<span class="code">{{ item.hwid_uuid }}</span>
{% else %}
-
{% endif %}
</td>
<td>
{% if item.username %}
<span class="badge assigned">Zugewiesen</span>
{% else %}
<span class="badge unassigned">Nicht zugewiesen</span>
{% endif %}
</td>
<td>
{% if item.user_id %}
<form method="POST" action="{{ url_for('remove_key', user_id=item.user_id) }}" class="remove-form" onsubmit="return confirm('Key für User {{ item.user_id }} wirklich löschen?');">
<button type="submit" class="btn-danger">Entfernen</button>
</form>
{% else %}
-
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="6">Keine Lizenz-Keys gefunden.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</article>
</section>
{% endblock %}
-36
View File
@@ -1,36 +0,0 @@
{% extends "base.html" %}
{% block title %}Meine Lizenzen{% endblock %}
{% block content %}
<section class="panel">
<h1>Meine Lizenzen</h1>
<p>Hier sehen Sie Ihre aktiven und abgelaufenen Lizenzinformationen.</p>
</section>
<section class="grid">
{% if licenses %}
{% for item in licenses %}
<article class="card">
<h3>{{ item.plan }} Lizenz</h3>
<p><strong>Schule:</strong> {{ item.school_name }}</p>
<p><strong>Lizenzschlüssel:</strong> {{ item.license_key }}</p>
<p><strong>Status:</strong> {{ item.status }}</p>
<p><strong>Gültig bis:</strong> {{ item.valid_until }}</p>
</article>
{% endfor %}
{% else %}
<article class="card">
<h3>Keine Lizenzen vorhanden</h3>
<p>Aktuell sind noch keine Lizenzen hinterlegt.</p>
</article>
{% endif %}
</section>
<style>
.panel { margin-bottom: 1rem; }
.grid { display: grid; gap: 0.8rem; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.card { background: #fff; border: 1px solid #d8e1e8; border-radius: 14px; padding: 1rem; }
.card p { margin-top: 0.35rem; }
</style>
{% endblock %}
+1 -1
View File
@@ -22,7 +22,7 @@
<h2>Beispiel 2: Verbundschule mit mehreren Standorten</h2>
<ul>
<li><strong>Ausgangslage:</strong> Unterschiedliche Abläufe je Standort, keine gemeinsame Fristenübersicht.</li>
<li><strong>Umsetzung:</strong> Einheitliche Prozesse für Inventar, Lizenzen und Supporttickets.</li>
<li><strong>Umsetzung:</strong> Einheitliche Prozesse für Inventar, Instanzen und Supporttickets.</li>
<li><strong>Ergebnis:</strong> Standortübergreifende Transparenz und bessere Planbarkeit in der Verwaltung.</li>
</ul>
</article>
+1 -5
View File
@@ -91,7 +91,7 @@
{% block content %}
<section class="auth-wrap">
<h1>Schulregistrierung</h1>
<p>Registrieren Sie Ihre Schule, um Buchungen, Lizenzen und Support zentral zu verwalten.</p>
<p>Registrieren Sie Ihre Schule, um Buchungen, Instanzen und Support zentral zu verwalten.</p>
<form method="POST" action="{{ url_for('register') }}">
<div class="field">
<label for="school_name">Name der Schule</label>
@@ -117,10 +117,6 @@
<label for="password_repeat">Passwort wiederholen</label>
<input id="password_repeat" name="password_repeat" type="password" autocomplete="new-password" required>
</div>
<label class="checkbox-line" for="activate_test_key">
<input id="activate_test_key" name="activate_test_key" type="checkbox" value="1">
<span>Direkt bei Registrierung einen Test-Key aktivieren</span>
</label>
<button class="auth-btn" type="submit">Konto erstellen</button>
</form>
<p class="auth-meta">Bereits registriert? <a href="{{ url_for('login') }}">Zum Login</a></p>