Gerätelimit drin

Dieser Commit ist enthalten in:
2025-06-11 01:13:40 +02:00
Ursprung 4b66d8b4b4
Commit a878d9b29c
9 geänderte Dateien mit 549 neuen und 19 gelöschten Zeilen

Datei anzeigen

@@ -60,7 +60,8 @@
"Bash(/home/rac00n/.npm-global/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/x64-linux/rg -n \"href=[''\"\"][/]?(dashboard)?[''\"\"]\" --type html)",
"Bash(/home/rac00n/.npm-global/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/x64-linux/rg -n \"Dashboard\" /mnt/c/Users/Administrator/Documents/GitHub/v2-Docker/v2_adminpanel/templates/resources.html)",
"Bash(/home/rac00n/.npm-global/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/x64-linux/rg -n \"Dashboard\" /mnt/c/Users/Administrator/Documents/GitHub/v2-Docker/v2_adminpanel/templates/profile.html /mnt/c/Users/Administrator/Documents/GitHub/v2-Docker/v2_adminpanel/templates/resource_metrics.html)",
"Bash(/home/rac00n/.npm-global/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/x64-linux/rg -n \"BACKUP|LOGIN_2FA_SUCCESS\" /mnt/c/Users/Administrator/Documents/GitHub/v2-Docker/v2_adminpanel/app.py)"
"Bash(/home/rac00n/.npm-global/lib/node_modules/@anthropic-ai/claude-code/vendor/ripgrep/x64-linux/rg -n \"BACKUP|LOGIN_2FA_SUCCESS\" /mnt/c/Users/Administrator/Documents/GitHub/v2-Docker/v2_adminpanel/app.py)",
"Bash(sed:*)"
],
"deny": []
}

Datei anzeigen

@@ -1,5 +1,49 @@
# v2-Docker Projekt Journal
## Letzte Änderungen (06.01.2025)
### Gerätelimit-Feature implementiert
- **Datenbank-Schema erweitert**:
- Neue Spalte `device_limit` in `licenses` Tabelle (Standard: 3, Range: 1-10)
- Neue Tabelle `device_registrations` für Hardware-ID Tracking
- Indizes für Performance-Optimierung hinzugefügt
- **UI-Anpassungen**:
- Einzellizenz-Formular: Dropdown für Gerätelimit (1-10 Geräte)
- Batch-Formular: Gerätelimit pro Lizenz auswählbar
- Lizenz-Bearbeitung: Gerätelimit änderbar
- Lizenz-Anzeige: Zeigt aktive Geräte (z.B. "💻 2/3")
- **Backend-Änderungen**:
- Lizenz-Erstellung speichert device_limit
- Batch-Erstellung berücksichtigt device_limit
- Lizenz-Update kann device_limit ändern
- API-Endpoints liefern Geräteinformationen
- **Migration**:
- Skript `migrate_device_limit.sql` erstellt
- Setzt device_limit = 3 für alle bestehenden Lizenzen
### Vollständig implementiert:
✅ Device Management UI (Geräte pro Lizenz anzeigen/verwalten)
✅ Device Validation Logic (Prüfung bei Geräte-Registrierung)
✅ API-Endpoints für Geräte-Registrierung/Deregistrierung
### API-Endpoints:
- `GET /api/license/<id>/devices` - Listet alle Geräte einer Lizenz
- `POST /api/license/<id>/register-device` - Registriert ein neues Gerät
- `POST /api/license/<id>/deactivate-device/<device_id>` - Deaktiviert ein Gerät
### Features:
- Geräte-Registrierung mit Hardware-ID Validierung
- Automatische Prüfung des Gerätelimits
- Reaktivierung deaktivierter Geräte möglich
- Geräte-Verwaltung UI mit Modal-Dialog
- Anzeige von Gerätename, OS, IP, Registrierungsdatum
- Admin kann Geräte manuell deaktivieren
---
## Projektübersicht
Lizenzmanagement-System für Social Media Account-Erstellungssoftware mit Docker-basierter Architektur.

Datei anzeigen

@@ -1485,6 +1485,7 @@ def create_license():
domain_count = int(request.form.get("domain_count", 1))
ipv4_count = int(request.form.get("ipv4_count", 1))
phone_count = int(request.form.get("phone_count", 1))
device_limit = int(request.form.get("device_limit", 3))
conn = get_connection()
cur = conn.cursor()
@@ -1536,11 +1537,11 @@ def create_license():
# Lizenz hinzufügen
cur.execute("""
INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active,
domain_count, ipv4_count, phone_count, is_test)
VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s)
domain_count, ipv4_count, phone_count, device_limit, is_test)
VALUES (%s, %s, %s, %s, %s, TRUE, %s, %s, %s, %s, %s)
RETURNING id
""", (license_key, customer_id, license_type, valid_from, valid_until,
domain_count, ipv4_count, phone_count, is_test))
domain_count, ipv4_count, phone_count, device_limit, is_test))
license_id = cur.fetchone()[0]
# Ressourcen zuweisen
@@ -1652,6 +1653,7 @@ def create_license():
'license_type': license_type,
'valid_from': valid_from,
'valid_until': valid_until,
'device_limit': device_limit,
'is_test': is_test
})
@@ -1711,6 +1713,7 @@ def batch_licenses():
domain_count = int(request.form.get("domain_count", 1))
ipv4_count = int(request.form.get("ipv4_count", 1))
phone_count = int(request.form.get("phone_count", 1))
device_limit = int(request.form.get("device_limit", 3))
# Sicherheitslimit
if quantity < 1 or quantity > 100:
@@ -1803,11 +1806,11 @@ def batch_licenses():
cur.execute("""
INSERT INTO licenses (license_key, customer_id, license_type, is_test,
valid_from, valid_until, is_active,
domain_count, ipv4_count, phone_count)
VALUES (%s, %s, %s, %s, %s, %s, true, %s, %s, %s)
domain_count, ipv4_count, phone_count, device_limit)
VALUES (%s, %s, %s, %s, %s, %s, true, %s, %s, %s, %s)
RETURNING id
""", (license_key, customer_id, license_type, is_test, valid_from, valid_until,
domain_count, ipv4_count, phone_count))
domain_count, ipv4_count, phone_count, device_limit))
license_id = cur.fetchone()[0]
# Ressourcen für diese Lizenz zuweisen
@@ -1983,7 +1986,7 @@ def edit_license(license_id):
if request.method == "POST":
# Alte Werte für Audit-Log abrufen
cur.execute("""
SELECT license_key, license_type, valid_from, valid_until, is_active, is_test
SELECT license_key, license_type, valid_from, valid_until, is_active, is_test, device_limit
FROM licenses WHERE id = %s
""", (license_id,))
old_license = cur.fetchone()
@@ -1995,13 +1998,14 @@ def edit_license(license_id):
valid_until = request.form["valid_until"]
is_active = request.form.get("is_active") == "on"
is_test = request.form.get("is_test") == "on"
device_limit = int(request.form.get("device_limit", 3))
cur.execute("""
UPDATE licenses
SET license_key = %s, license_type = %s, valid_from = %s,
valid_until = %s, is_active = %s, is_test = %s
valid_until = %s, is_active = %s, is_test = %s, device_limit = %s
WHERE id = %s
""", (license_key, license_type, valid_from, valid_until, is_active, is_test, license_id))
""", (license_key, license_type, valid_from, valid_until, is_active, is_test, device_limit, license_id))
conn.commit()
@@ -2013,7 +2017,8 @@ def edit_license(license_id):
'valid_from': str(old_license[2]),
'valid_until': str(old_license[3]),
'is_active': old_license[4],
'is_test': old_license[5]
'is_test': old_license[5],
'device_limit': old_license[6]
},
new_values={
'license_key': license_key,
@@ -2021,7 +2026,8 @@ def edit_license(license_id):
'valid_from': valid_from,
'valid_until': valid_until,
'is_active': is_active,
'is_test': is_test
'is_test': is_test,
'device_limit': device_limit
})
cur.close()
@@ -2048,7 +2054,7 @@ def edit_license(license_id):
# Get license data
cur.execute("""
SELECT l.id, l.license_key, c.name, c.email, l.license_type,
l.valid_from, l.valid_until, l.is_active, c.id, l.is_test
l.valid_from, l.valid_until, l.is_active, c.id, l.is_test, l.device_limit
FROM licenses l
JOIN customers c ON l.customer_id = c.id
WHERE l.id = %s
@@ -2343,7 +2349,9 @@ def customers_licenses():
END as status,
l.domain_count,
l.ipv4_count,
l.phone_count
l.phone_count,
l.device_limit,
(SELECT COUNT(*) FROM device_registrations WHERE license_id = l.id AND is_active = TRUE) as active_devices
FROM licenses l
WHERE l.customer_id = %s
ORDER BY l.created_at DESC, l.id DESC
@@ -2384,7 +2392,9 @@ def api_customer_licenses(customer_id):
END as status,
l.domain_count,
l.ipv4_count,
l.phone_count
l.phone_count,
l.device_limit,
(SELECT COUNT(*) FROM device_registrations WHERE license_id = l.id AND is_active = TRUE) as active_devices
FROM licenses l
WHERE l.customer_id = %s
ORDER BY l.created_at DESC, l.id DESC
@@ -2434,6 +2444,8 @@ def api_customer_licenses(customer_id):
'domain_count': row[7],
'ipv4_count': row[8],
'phone_count': row[9],
'device_limit': row[10],
'active_devices': row[11],
'resources': resources
})
@@ -3711,6 +3723,218 @@ def bulk_deactivate_licenses():
except Exception as e:
return jsonify({'success': False, 'message': str(e)}), 500
@app.route("/api/license/<int:license_id>/devices")
@login_required
def get_license_devices(license_id):
"""Hole alle registrierten Geräte einer Lizenz"""
try:
conn = get_connection()
cur = conn.cursor()
# Prüfe ob Lizenz existiert und hole device_limit
cur.execute("""
SELECT device_limit FROM licenses WHERE id = %s
""", (license_id,))
license_data = cur.fetchone()
if not license_data:
return jsonify({'success': False, 'message': 'Lizenz nicht gefunden'}), 404
device_limit = license_data[0]
# Hole alle Geräte für diese Lizenz
cur.execute("""
SELECT id, hardware_id, device_name, operating_system,
first_seen, last_seen, is_active, ip_address
FROM device_registrations
WHERE license_id = %s
ORDER BY is_active DESC, last_seen DESC
""", (license_id,))
devices = []
for row in cur.fetchall():
devices.append({
'id': row[0],
'hardware_id': row[1],
'device_name': row[2] or 'Unbekanntes Gerät',
'operating_system': row[3] or 'Unbekannt',
'first_seen': row[4].strftime('%d.%m.%Y %H:%M') if row[4] else '',
'last_seen': row[5].strftime('%d.%m.%Y %H:%M') if row[5] else '',
'is_active': row[6],
'ip_address': row[7] or '-'
})
cur.close()
conn.close()
return jsonify({
'success': True,
'devices': devices,
'device_limit': device_limit,
'active_count': sum(1 for d in devices if d['is_active'])
})
except Exception as e:
logging.error(f"Fehler beim Abrufen der Geräte: {str(e)}")
return jsonify({'success': False, 'message': 'Fehler beim Abrufen der Geräte'}), 500
@app.route("/api/license/<int:license_id>/register-device", methods=["POST"])
def register_device(license_id):
"""Registriere ein neues Gerät für eine Lizenz"""
try:
data = request.get_json()
hardware_id = data.get('hardware_id')
device_name = data.get('device_name', '')
operating_system = data.get('operating_system', '')
if not hardware_id:
return jsonify({'success': False, 'message': 'Hardware-ID fehlt'}), 400
conn = get_connection()
cur = conn.cursor()
# Prüfe ob Lizenz existiert und aktiv ist
cur.execute("""
SELECT device_limit, is_active, valid_until
FROM licenses
WHERE id = %s
""", (license_id,))
license_data = cur.fetchone()
if not license_data:
return jsonify({'success': False, 'message': 'Lizenz nicht gefunden'}), 404
device_limit, is_active, valid_until = license_data
# Prüfe ob Lizenz aktiv und gültig ist
if not is_active:
return jsonify({'success': False, 'message': 'Lizenz ist deaktiviert'}), 403
if valid_until < datetime.now(ZoneInfo("Europe/Berlin")).date():
return jsonify({'success': False, 'message': 'Lizenz ist abgelaufen'}), 403
# Prüfe ob Gerät bereits registriert ist
cur.execute("""
SELECT id, is_active FROM device_registrations
WHERE license_id = %s AND hardware_id = %s
""", (license_id, hardware_id))
existing_device = cur.fetchone()
if existing_device:
device_id, is_device_active = existing_device
if is_device_active:
# Gerät ist bereits aktiv, update last_seen
cur.execute("""
UPDATE device_registrations
SET last_seen = CURRENT_TIMESTAMP,
ip_address = %s,
user_agent = %s
WHERE id = %s
""", (get_client_ip(), request.headers.get('User-Agent', ''), device_id))
conn.commit()
return jsonify({'success': True, 'message': 'Gerät bereits registriert', 'device_id': device_id})
else:
# Gerät war deaktiviert, prüfe ob wir es reaktivieren können
cur.execute("""
SELECT COUNT(*) FROM device_registrations
WHERE license_id = %s AND is_active = TRUE
""", (license_id,))
active_count = cur.fetchone()[0]
if active_count >= device_limit:
return jsonify({'success': False, 'message': f'Gerätelimit erreicht ({device_limit} Geräte)'}), 403
# Reaktiviere das Gerät
cur.execute("""
UPDATE device_registrations
SET is_active = TRUE,
last_seen = CURRENT_TIMESTAMP,
deactivated_at = NULL,
deactivated_by = NULL,
ip_address = %s,
user_agent = %s
WHERE id = %s
""", (get_client_ip(), request.headers.get('User-Agent', ''), device_id))
conn.commit()
return jsonify({'success': True, 'message': 'Gerät reaktiviert', 'device_id': device_id})
# Neues Gerät - prüfe Gerätelimit
cur.execute("""
SELECT COUNT(*) FROM device_registrations
WHERE license_id = %s AND is_active = TRUE
""", (license_id,))
active_count = cur.fetchone()[0]
if active_count >= device_limit:
return jsonify({'success': False, 'message': f'Gerätelimit erreicht ({device_limit} Geräte)'}), 403
# Registriere neues Gerät
cur.execute("""
INSERT INTO device_registrations
(license_id, hardware_id, device_name, operating_system, ip_address, user_agent)
VALUES (%s, %s, %s, %s, %s, %s)
RETURNING id
""", (license_id, hardware_id, device_name, operating_system,
get_client_ip(), request.headers.get('User-Agent', '')))
device_id = cur.fetchone()[0]
conn.commit()
# Audit Log
log_audit('DEVICE_REGISTER', 'device', device_id,
new_values={'license_id': license_id, 'hardware_id': hardware_id})
cur.close()
conn.close()
return jsonify({'success': True, 'message': 'Gerät erfolgreich registriert', 'device_id': device_id})
except Exception as e:
logging.error(f"Fehler bei Geräte-Registrierung: {str(e)}")
return jsonify({'success': False, 'message': 'Fehler bei der Registrierung'}), 500
@app.route("/api/license/<int:license_id>/deactivate-device/<int:device_id>", methods=["POST"])
@login_required
def deactivate_device(license_id, device_id):
"""Deaktiviere ein registriertes Gerät"""
try:
conn = get_connection()
cur = conn.cursor()
# Prüfe ob das Gerät zu dieser Lizenz gehört
cur.execute("""
SELECT id FROM device_registrations
WHERE id = %s AND license_id = %s AND is_active = TRUE
""", (device_id, license_id))
if not cur.fetchone():
return jsonify({'success': False, 'message': 'Gerät nicht gefunden oder bereits deaktiviert'}), 404
# Deaktiviere das Gerät
cur.execute("""
UPDATE device_registrations
SET is_active = FALSE,
deactivated_at = CURRENT_TIMESTAMP,
deactivated_by = %s
WHERE id = %s
""", (session['username'], device_id))
conn.commit()
# Audit Log
log_audit('DEVICE_DEACTIVATE', 'device', device_id,
old_values={'is_active': True},
new_values={'is_active': False})
cur.close()
conn.close()
return jsonify({'success': True, 'message': 'Gerät erfolgreich deaktiviert'})
except Exception as e:
logging.error(f"Fehler beim Deaktivieren des Geräts: {str(e)}")
return jsonify({'success': False, 'message': 'Fehler beim Deaktivieren'}), 500
@app.route("/api/licenses/bulk-delete", methods=["POST"])
@login_required
def bulk_delete_licenses():

Datei anzeigen

@@ -173,6 +173,38 @@ BEGIN
END IF;
END $$;
-- Erweiterung der licenses Tabelle um device_limit
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'licenses' AND column_name = 'device_limit') THEN
ALTER TABLE licenses
ADD COLUMN device_limit INTEGER DEFAULT 3 CHECK (device_limit >= 1 AND device_limit <= 10);
END IF;
END $$;
-- Tabelle für Geräte-Registrierungen
CREATE TABLE IF NOT EXISTS device_registrations (
id SERIAL PRIMARY KEY,
license_id INTEGER REFERENCES licenses(id) ON DELETE CASCADE,
hardware_id TEXT NOT NULL,
device_name TEXT,
operating_system TEXT,
first_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
last_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
is_active BOOLEAN DEFAULT TRUE,
deactivated_at TIMESTAMP WITH TIME ZONE,
deactivated_by TEXT,
ip_address TEXT,
user_agent TEXT,
UNIQUE(license_id, hardware_id)
);
-- Indizes für device_registrations
CREATE INDEX IF NOT EXISTS idx_device_license ON device_registrations(license_id);
CREATE INDEX IF NOT EXISTS idx_device_hardware ON device_registrations(hardware_id);
CREATE INDEX IF NOT EXISTS idx_device_active ON device_registrations(license_id, is_active) WHERE is_active = TRUE;
-- Indizes für Performance
CREATE INDEX IF NOT EXISTS idx_resource_status ON resource_pools(status);
CREATE INDEX IF NOT EXISTS idx_resource_type_status ON resource_pools(resource_type, status);

Datei anzeigen

@@ -0,0 +1,13 @@
-- Migration: Setze device_limit für bestehende Test-Lizenzen auf 3
-- Dieses Script wird nur einmal ausgeführt, um bestehende Lizenzen zu aktualisieren
-- Setze device_limit = 3 für alle bestehenden Lizenzen, die noch keinen Wert haben
UPDATE licenses
SET device_limit = 3
WHERE device_limit IS NULL;
-- Bestätige die Änderung
SELECT COUNT(*) as updated_licenses,
COUNT(CASE WHEN is_test = TRUE THEN 1 END) as test_licenses_updated
FROM licenses
WHERE device_limit = 3;

Datei anzeigen

@@ -147,6 +147,32 @@
</div>
</div>
<!-- Device Limit -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-laptop"></i> Gerätelimit pro Lizenz
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label for="deviceLimit" class="form-label">
Maximale Anzahl Geräte pro Lizenz
</label>
<select class="form-select" id="deviceLimit" name="device_limit" required>
{% for i in range(1, 11) %}
<option value="{{ i }}" {% if i == 3 %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Jede generierte Lizenz kann auf maximal dieser Anzahl von Geräten gleichzeitig aktiviert werden.
</small>
</div>
</div>
</div>
</div>
<!-- Test Data Checkbox -->
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test">

Datei anzeigen

@@ -168,18 +168,24 @@
</div>
{% endif %}
{% if license[9] > 0 %}
<div class="d-inline-block" data-bs-toggle="tooltip" title="Telefonnummern">
<div class="d-inline-block me-2" data-bs-toggle="tooltip" title="Telefonnummern">
📱 {{ license[9] }}
</div>
{% endif %}
<div class="d-inline-block" data-bs-toggle="tooltip" title="Geräte">
💻 {{ license[11] }}/{{ license[10] }}
</div>
</div>
</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus({{ license[0] }}, {{ license[5] }})">
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus({{ license[0] }}, {{ license[5] }})" title="Aktivieren/Deaktivieren">
<i class="bi bi-power"></i>
</button>
<a href="/license/edit/{{ license[0] }}{% if request.args.get('show_test') %}?ref=customers-licenses&show_test=true{% endif %}" class="btn btn-outline-secondary">
<button class="btn btn-outline-info" onclick="showDeviceManagement({{ license[0] }})" title="Geräte verwalten">
<i class="bi bi-laptop"></i>
</button>
<a href="/license/edit/{{ license[0] }}{% if request.args.get('show_test') %}?ref=customers-licenses&show_test=true{% endif %}" class="btn btn-outline-secondary" title="Bearbeiten">
<i class="bi bi-pencil"></i>
</a>
</div>
@@ -253,6 +259,28 @@
</div>
</div>
<!-- Modal für Geräte-Verwaltung -->
<div class="modal fade" id="deviceManagementModal" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Geräte verwalten</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="deviceManagementContent">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Lädt...</span>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Schließen</button>
</div>
</div>
</div>
</div>
<style>
.customer-item {
transition: all 0.2s ease;
@@ -433,6 +461,11 @@ function updateLicenseView(customerId, licenses) {
}
}
// Geräte-Anzeige hinzufügen
resourcesHtml += `<div class="resource-group">
<span class="resource-icon" data-bs-toggle="tooltip" title="Geräte">💻 ${license.active_devices}/${license.device_limit}</span>
</div>`;
html += `
<tr data-license-id="${license.id}">
<td>
@@ -453,7 +486,10 @@ function updateLicenseView(customerId, licenses) {
<button class="btn btn-outline-primary" onclick="toggleLicenseStatus(${license.id}, ${license.is_active})" title="Status ändern">
<i class="bi bi-power"></i>
</button>
<button class="btn btn-outline-info" onclick="showResourceManagement(${license.id})" title="Ressourcen verwalten">
<button class="btn btn-outline-info" onclick="showDeviceManagement(${license.id})" title="Geräte verwalten">
<i class="bi bi-laptop"></i>
</button>
<button class="btn btn-outline-secondary" onclick="showResourceManagement(${license.id})" title="Ressourcen verwalten">
<i class="bi bi-gear"></i>
</button>
<a href="/license/edit/${license.id}${window.location.search ? '?ref=customers-licenses&' + window.location.search.substring(1) : ''}" class="btn btn-outline-secondary" title="Bearbeiten">
@@ -774,5 +810,124 @@ function quarantineResource(resourceId, resourceValue) {
function saveResourceChanges() {
alert('Diese Funktion wird noch implementiert');
}
// Geräte-Verwaltung anzeigen
function showDeviceManagement(licenseId) {
const modal = new bootstrap.Modal(document.getElementById('deviceManagementModal'));
modal.show();
// Lade Geräte-Daten
fetch(`/api/license/${licenseId}/devices`)
.then(response => response.json())
.then(data => {
if (data.success) {
let content = `
<div class="mb-3">
<div class="alert alert-info">
<strong>Gerätelimit:</strong> ${data.active_count} von ${data.device_limit} Geräten aktiv
</div>
</div>`;
if (data.devices.length === 0) {
content += `
<div class="text-center py-4">
<i class="bi bi-laptop text-muted" style="font-size: 3rem;"></i>
<p class="text-muted mt-3">Noch keine Geräte registriert</p>
</div>`;
} else {
content += `
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Gerätename</th>
<th>Hardware-ID</th>
<th>Betriebssystem</th>
<th>Erste Registrierung</th>
<th>Letzte Aktivität</th>
<th>IP-Adresse</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>`;
data.devices.forEach(device => {
const statusBadge = device.is_active
? '<span class="badge bg-success">Aktiv</span>'
: '<span class="badge bg-secondary">Deaktiviert</span>';
const actionButton = device.is_active
? `<button class="btn btn-sm btn-danger" onclick="deactivateDevice(${licenseId}, ${device.id}, '${device.device_name}')">
<i class="bi bi-x-circle"></i> Deaktivieren
</button>`
: '<span class="text-muted">-</span>';
content += `
<tr>
<td>${device.device_name}</td>
<td><small class="text-muted">${device.hardware_id.substring(0, 12)}...</small></td>
<td>${device.operating_system}</td>
<td>${device.first_seen}</td>
<td>${device.last_seen}</td>
<td>${device.ip_address}</td>
<td>${statusBadge}</td>
<td>${actionButton}</td>
</tr>`;
});
content += `
</tbody>
</table>
</div>`;
}
document.getElementById('deviceManagementContent').innerHTML = content;
} else {
document.getElementById('deviceManagementContent').innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i> ${data.message || 'Fehler beim Laden der Geräte'}
</div>`;
}
})
.catch(error => {
console.error('Error loading devices:', error);
document.getElementById('deviceManagementContent').innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle"></i> Fehler beim Laden der Geräte
</div>`;
});
}
// Gerät deaktivieren
function deactivateDevice(licenseId, deviceId, deviceName) {
if (!confirm(`Möchten Sie das Gerät "${deviceName}" wirklich deaktivieren?`)) {
return;
}
fetch(`/api/license/${licenseId}/deactivate-device/${deviceId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Aktualisiere die Geräte-Ansicht
showDeviceManagement(licenseId);
// Aktualisiere die Lizenz-Anzeige wenn möglich
if (typeof loadCustomerLicenses === 'function' && currentCustomerId) {
loadCustomerLicenses(currentCustomerId);
}
} else {
alert(data.message || 'Fehler beim Deaktivieren des Geräts');
}
})
.catch(error => {
console.error('Error deactivating device:', error);
alert('Fehler beim Deaktivieren des Geräts');
});
}
</script>
{% endblock %}

Datei anzeigen

@@ -54,6 +54,15 @@
</label>
</div>
</div>
<div class="col-md-6">
<label for="deviceLimit" class="form-label">Gerätelimit</label>
<select class="form-select" id="deviceLimit" name="device_limit" required>
{% for i in range(1, 11) %}
<option value="{{ i }}" {% if license[10] == i %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
{% endfor %}
</select>
<small class="form-text text-muted">Maximale Anzahl gleichzeitig aktiver Geräte</small>
</div>
</div>
<div class="form-check mt-3">

Datei anzeigen

@@ -126,6 +126,32 @@
</div>
</div>
<!-- Device Limit -->
<div class="card mt-4">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-laptop"></i> Gerätelimit
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6">
<label for="deviceLimit" class="form-label">
Maximale Anzahl Geräte
</label>
<select class="form-select" id="deviceLimit" name="device_limit" required>
{% for i in range(1, 11) %}
<option value="{{ i }}" {% if i == 3 %}selected{% endif %}>{{ i }} {% if i == 1 %}Gerät{% else %}Geräte{% endif %}</option>
{% endfor %}
</select>
<small class="form-text text-muted">
Anzahl der Geräte, auf denen die Lizenz gleichzeitig aktiviert sein kann.
</small>
</div>
</div>
</div>
</div>
<!-- Test Data Checkbox -->
<div class="form-check mt-3">
<input class="form-check-input" type="checkbox" id="isTest" name="is_test">