lizenzserver API gedöns

Dieser Commit ist enthalten in:
2025-06-22 00:53:05 +02:00
Ursprung 6d1577c989
Commit b420452551
8 geänderte Dateien mit 914 neuen und 195 gelöschten Zeilen

Datei anzeigen

@@ -1,22 +1,114 @@
{% extends "base.html" %}
{% block title %}Lizenzserver Konfiguration{% endblock %}
{% block title %}Administration{% endblock %}
{% block content %}
<div class="row mb-4">
<div class="col-12">
<h1 class="h3">Lizenzserver Konfiguration</h1>
<h1 class="h3">Administration</h1>
</div>
</div>
<!-- Feature Flags -->
<!-- Account Forger Configuration -->
<div class="row mb-4">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Feature Flags</h5>
<div class="card-header bg-primary text-white">
<h5 class="mb-0">Account Forger Konfiguration</h5>
</div>
<div class="card-body">
<form method="post" action="{{ url_for('admin.update_client_config') }}" class="row g-3">
<div class="col-md-6">
<label class="form-label">Aktuelle Version</label>
<input type="text" class="form-control" name="current_version"
value="{{ client_config[5] if client_config else '1.0.0' }}"
pattern="^\d+\.\d+\.\d+$" required>
</div>
<div class="col-md-6">
<label class="form-label">Minimum Version</label>
<input type="text" class="form-control" name="minimum_version"
value="{{ client_config[6] if client_config else '1.0.0' }}"
pattern="^\d+\.\d+\.\d+$" required>
</div>
<div class="col-12">
<label class="form-label">API Key</label>
<div class="input-group">
<input type="text" class="form-control" value="{{ client_config[2] if client_config else 'Nicht konfiguriert' }}" readonly>
{% if client_config %}
<button class="btn btn-outline-secondary" type="button" onclick="copyToClipboard('{{ client_config[2] }}')">
<i class="bi bi-clipboard"></i> Kopieren
</button>
{% endif %}
</div>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header bg-info text-white d-flex justify-content-between align-items-center">
<h5 class="mb-0">Aktive Sitzungen</h5>
<div>
<span class="badge bg-white text-dark" id="sessionCount">{{ active_sessions|length if active_sessions else 0 }}</span>
<a href="{{ url_for('admin.license_sessions') }}" class="btn btn-sm btn-light ms-2">Alle anzeigen</a>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Kunde</th>
<th>Version</th>
<th>Letztes Heartbeat</th>
<th>Status</th>
</tr>
</thead>
<tbody id="sessionTableBody">
{% if active_sessions %}
{% for session in active_sessions[:5] %}
<tr>
<td>{{ session[3] or 'Unbekannt' }}</td>
<td>{{ session[6] }}</td>
<td>{{ session[8].strftime('%H:%M:%S') }}</td>
<td>
{% if session[9] < 90 %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-warning">Timeout</span>
{% endif %}
</td>
</tr>
{% endfor %}
{% else %}
<tr>
<td colspan="4" class="text-center text-muted">Keine aktiven Sitzungen</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Technical Settings (collapsible) -->
<div class="accordion mb-4" id="technicalSettings">
<!-- Feature Flags -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#featureFlags">
Feature Flags
</button>
</h2>
<div id="featureFlags" class="accordion-collapse collapse" data-bs-parent="#technicalSettings">
<div class="accordion-body">
<div class="table-responsive">
<table class="table">
<thead>
@@ -59,66 +151,15 @@
</div>
</div>
<!-- API Clients -->
<div class="col-md-6">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">API Clients</h5>
<button class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#newApiClientModal">
<i class="bi bi-plus"></i> Neuer Client
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>API Key</th>
<th>Status</th>
<th>Erstellt</th>
</tr>
</thead>
<tbody>
{% for client in api_clients %}
<tr>
<td>{{ client[1] }}</td>
<td>
<code>{{ client[2][:12] }}...</code>
<button class="btn btn-sm btn-link" onclick="copyToClipboard('{{ client[2] }}')">
<i class="bi bi-clipboard"></i>
</button>
</td>
<td>
{% if client[3] %}
<span class="badge bg-success">Aktiv</span>
{% else %}
<span class="badge bg-danger">Inaktiv</span>
{% endif %}
</td>
<td>{{ client[4].strftime('%d.%m.%Y') if client[4] else '-' }}</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="text-center text-muted">Keine API Clients vorhanden</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<!-- Rate Limits -->
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5 class="mb-0">Rate Limits</h5>
</div>
<div class="card-body">
<!-- Rate Limits -->
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#rateLimits">
Rate Limits
</button>
</h2>
<div id="rateLimits" class="accordion-collapse collapse" data-bs-parent="#technicalSettings">
<div class="accordion-body">
<div class="table-responsive">
<table class="table">
<thead>
@@ -128,7 +169,6 @@
<th>Requests/Stunde</th>
<th>Requests/Tag</th>
<th>Burst Size</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
@@ -139,15 +179,10 @@
<td>{{ limit[3] }}</td>
<td>{{ limit[4] }}</td>
<td>{{ limit[5] }}</td>
<td>
<button class="btn btn-sm btn-warning" onclick="editRateLimit('{{ limit[0] }}', {{ limit[2] }}, {{ limit[3] }}, {{ limit[4] }})">
Bearbeiten
</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center text-muted">Keine Rate Limits konfiguriert</td>
<td colspan="5" class="text-center text-muted">Keine Rate Limits konfiguriert</td>
</tr>
{% endfor %}
</tbody>
@@ -157,83 +192,57 @@
</div>
</div>
</div>
<!-- New API Client Modal -->
<div class="modal fade" id="newApiClientModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form method="post" action="/lizenzserver/config/api-client">
<div class="modal-header">
<h5 class="modal-title">Neuer API Client</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Beschreibung</label>
<textarea name="description" class="form-control" rows="2"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-primary">Erstellen</button>
</div>
</form>
</div>
</div>
</div>
<!-- Edit Rate Limit Modal -->
<div class="modal fade" id="editRateLimitModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="editRateLimitForm" method="post">
<div class="modal-header">
<h5 class="modal-title">Rate Limit bearbeiten</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">Requests pro Minute</label>
<input type="number" name="requests_per_minute" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Requests pro Stunde</label>
<input type="number" name="requests_per_hour" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Requests pro Tag</label>
<input type="number" name="requests_per_day" class="form-control" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(function() {
alert('API Key wurde in die Zwischenablage kopiert!');
// Show success message instead of alert
const button = event.target.closest('button');
const originalText = button.innerHTML;
button.innerHTML = '<i class="bi bi-check"></i> Kopiert!';
button.classList.remove('btn-outline-secondary');
button.classList.add('btn-success');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-outline-secondary');
}, 2000);
});
}
function editRateLimit(id, rpm, rph, rpd) {
const form = document.getElementById('editRateLimitForm');
form.action = `/lizenzserver/config/rate-limit/${id}`;
form.requests_per_minute.value = rpm;
form.requests_per_hour.value = rph;
form.requests_per_day.value = rpd;
new bootstrap.Modal(document.getElementById('editRateLimitModal')).show();
// Auto-refresh sessions every 30 seconds
function refreshSessions() {
fetch('{{ url_for("admin.license_live_stats") }}')
.then(response => response.json())
.then(data => {
document.getElementById('sessionCount').textContent = data.active_licenses || 0;
// Update session table
const tbody = document.getElementById('sessionTableBody');
if (data.latest_sessions && data.latest_sessions.length > 0) {
tbody.innerHTML = data.latest_sessions.map(session => `
<tr>
<td>${session.customer_name || 'Unbekannt'}</td>
<td>${session.version}</td>
<td>${session.last_heartbeat}</td>
<td>
${session.seconds_since < 90
? '<span class="badge bg-success">Aktiv</span>'
: '<span class="badge bg-warning">Timeout</span>'}
</td>
</tr>
`).join('');
} else {
tbody.innerHTML = '<tr><td colspan="4" class="text-center text-muted">Keine aktiven Sitzungen</td></tr>';
}
})
.catch(error => console.error('Error refreshing sessions:', error));
}
// Refresh sessions every 30 seconds
setInterval(refreshSessions, 30000);
</script>
{% endblock %}

Datei anzeigen

@@ -0,0 +1,151 @@
{% extends "base.html" %}
{% block title %}Aktive Lizenzsitzungen{% endblock %}
{% block content %}
<div class="container-fluid">
<h1>Lizenzsitzungen</h1>
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5>Aktive Sitzungen</h5>
</div>
<div class="card-body">
{% if active_sessions %}
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Lizenzschlüssel</th>
<th>Kunde</th>
<th>Hardware ID</th>
<th>IP-Adresse</th>
<th>Version</th>
<th>Gestartet</th>
<th>Letztes Heartbeat</th>
<th>Status</th>
<th>Aktion</th>
</tr>
</thead>
<tbody>
{% for session in active_sessions %}
<tr>
<td><code>{{ session[2][:8] }}...</code></td>
<td>{{ session[3] or 'Unbekannt' }}</td>
<td><code>{{ session[4][:12] }}...</code></td>
<td>{{ session[5] or 'Unbekannt' }}</td>
<td>{{ session[6] }}</td>
<td>{{ session[7].strftime('%H:%M:%S') }}</td>
<td>{{ session[8].strftime('%H:%M:%S') }}</td>
<td>
{% if session[9] < 90 %}
<span class="badge bg-success">Aktiv</span>
{% elif session[9] < 120 %}
<span class="badge bg-warning">Timeout bald</span>
{% else %}
<span class="badge bg-danger">Timeout</span>
{% endif %}
</td>
<td>
{% if session.get('username') in ['rac00n', 'w@rh@mm3r'] %}
<form method="POST" action="{{ url_for('admin.terminate_session', session_id=session[0]) }}"
style="display: inline;" onsubmit="return confirm('Sitzung wirklich beenden?');">
<button type="submit" class="btn btn-sm btn-danger">Beenden</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">Keine aktiven Sitzungen vorhanden.</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card">
<div class="card-header">
<h5>Sitzungsverlauf (letzte 24 Stunden)</h5>
</div>
<div class="card-body">
{% if session_history %}
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Lizenzschlüssel</th>
<th>Kunde</th>
<th>Hardware ID</th>
<th>IP-Adresse</th>
<th>Version</th>
<th>Gestartet</th>
<th>Beendet</th>
<th>Dauer</th>
<th>Grund</th>
</tr>
</thead>
<tbody>
{% for hist in session_history %}
<tr>
<td><code>{{ hist[1][:8] }}...</code></td>
<td>{{ hist[2] or 'Unbekannt' }}</td>
<td><code>{{ hist[3][:12] }}...</code></td>
<td>{{ hist[4] or 'Unbekannt' }}</td>
<td>{{ hist[5] }}</td>
<td>{{ hist[6].strftime('%d.%m %H:%M') }}</td>
<td>{{ hist[7].strftime('%d.%m %H:%M') }}</td>
<td>
{% set duration = hist[9] %}
{% if duration < 60 %}
{{ duration|int }}s
{% elif duration < 3600 %}
{{ (duration / 60)|int }}m
{% else %}
{{ (duration / 3600)|round(1) }}h
{% endif %}
</td>
<td>
{% if hist[8] == 'normal' %}
<span class="badge bg-success">Normal</span>
{% elif hist[8] == 'timeout' %}
<span class="badge bg-warning">Timeout</span>
{% elif hist[8] == 'forced' %}
<span class="badge bg-danger">Erzwungen</span>
{% elif hist[8] == 'replaced' %}
<span class="badge bg-info">Ersetzt</span>
{% else %}
{{ hist[8] }}
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">Keine Sitzungen in den letzten 24 Stunden.</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="mt-3">
<a href="{{ url_for('admin.license_config') }}" class="btn btn-secondary">Zurück zur Konfiguration</a>
</div>
</div>
<script>
// Auto-refresh every 30 seconds
setTimeout(function() {
location.reload();
}, 30000);
</script>
{% endblock %}