Löschen Lizenz Schutz

Dieser Commit ist enthalten in:
2025-06-21 16:41:08 +02:00
Ursprung 1451a23ff3
Commit fec588ba06
3 geänderte Dateien mit 177 neuen und 22 gelöschten Zeilen

Datei anzeigen

@@ -375,9 +375,11 @@ def deactivate_device(license_id, device_id):
@api_bp.route("/licenses/bulk-delete", methods=["POST"]) @api_bp.route("/licenses/bulk-delete", methods=["POST"])
@login_required @login_required
def bulk_delete_licenses(): def bulk_delete_licenses():
"""Lösche mehrere Lizenzen gleichzeitig""" """Lösche mehrere Lizenzen gleichzeitig mit Sicherheitsprüfungen"""
data = request.get_json() data = request.get_json()
license_ids = data.get('license_ids', []) # Accept both 'ids' (from frontend) and 'license_ids' for compatibility
license_ids = data.get('ids', data.get('license_ids', []))
force_delete = data.get('force', False)
if not license_ids: if not license_ids:
return jsonify({'error': 'Keine Lizenzen ausgewählt'}), 400 return jsonify({'error': 'Keine Lizenzen ausgewählt'}), 400
@@ -387,35 +389,120 @@ def bulk_delete_licenses():
try: try:
deleted_count = 0 deleted_count = 0
skipped_licenses = []
active_licenses = []
recently_used_licenses = []
for license_id in license_ids: for license_id in license_ids:
# Hole Lizenz-Info für Audit # Hole vollständige Lizenz-Info
cur.execute("SELECT license_key FROM licenses WHERE id = %s", (license_id,)) cur.execute("""
SELECT l.id, l.license_key, l.is_active, l.is_test,
c.name as customer_name
FROM licenses l
LEFT JOIN customers c ON l.customer_id = c.id
WHERE l.id = %s
""", (license_id,))
result = cur.fetchone() result = cur.fetchone()
if result: if not result:
license_key = result[0] continue
# Lösche Sessions license_id, license_key, is_active, is_test, customer_name = result
# Safety check: Don't delete active licenses unless forced
if is_active and not force_delete:
active_licenses.append(f"{license_key} ({customer_name})")
skipped_licenses.append(license_id)
continue
# Check for recent activity (heartbeats in last 24 hours)
if not force_delete:
try:
cur.execute("""
SELECT COUNT(*)
FROM license_heartbeats
WHERE license_id = %s
AND timestamp > NOW() - INTERVAL '24 hours'
""", (license_id,))
recent_heartbeats = cur.fetchone()[0]
if recent_heartbeats > 0:
recently_used_licenses.append(f"{license_key} ({recent_heartbeats} activities)")
skipped_licenses.append(license_id)
continue
except:
# If heartbeats table doesn't exist, continue
pass
# Check for active devices
if not force_delete:
try:
cur.execute("""
SELECT COUNT(*)
FROM activations
WHERE license_id = %s
AND is_active = true
""", (license_id,))
active_devices = cur.fetchone()[0]
if active_devices > 0:
recently_used_licenses.append(f"{license_key} ({active_devices} active devices)")
skipped_licenses.append(license_id)
continue
except:
# If activations table doesn't exist, continue
pass
# Delete associated data
cur.execute("DELETE FROM sessions WHERE license_key = %s", (license_key,)) cur.execute("DELETE FROM sessions WHERE license_key = %s", (license_key,))
# Lösche Geräte-Registrierungen try:
cur.execute("DELETE FROM device_registrations WHERE license_id = %s", (license_id,)) cur.execute("DELETE FROM device_registrations WHERE license_id = %s", (license_id,))
except:
pass
# Lösche Lizenz try:
cur.execute("DELETE FROM license_heartbeats WHERE license_id = %s", (license_id,))
except:
pass
try:
cur.execute("DELETE FROM activations WHERE license_id = %s", (license_id,))
except:
pass
# Delete the license
cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,)) cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,))
# Audit-Log # Audit-Log
log_audit('BULK_DELETE', 'license', license_id, log_audit('BULK_DELETE', 'license', license_id,
old_values={'license_key': license_key}) old_values={
'license_key': license_key,
'customer_name': customer_name,
'was_active': is_active,
'forced': force_delete
})
deleted_count += 1 deleted_count += 1
conn.commit() conn.commit()
# Build response message
message = f"{deleted_count} Lizenz(en) gelöscht."
warnings = []
if active_licenses:
warnings.append(f"Aktive Lizenzen übersprungen: {', '.join(active_licenses[:3])}{'...' if len(active_licenses) > 3 else ''}")
if recently_used_licenses:
warnings.append(f"Kürzlich genutzte Lizenzen übersprungen: {', '.join(recently_used_licenses[:3])}{'...' if len(recently_used_licenses) > 3 else ''}")
return jsonify({ return jsonify({
'success': True, 'success': True,
'deleted_count': deleted_count 'deleted_count': deleted_count,
'skipped_count': len(skipped_licenses),
'message': message,
'warnings': warnings
}) })
except Exception as e: except Exception as e:

Datei anzeigen

@@ -175,6 +175,9 @@ def edit_license(license_id):
@license_bp.route("/license/delete/<int:license_id>", methods=["POST"]) @license_bp.route("/license/delete/<int:license_id>", methods=["POST"])
@login_required @login_required
def delete_license(license_id): def delete_license(license_id):
# Check for force parameter
force_delete = request.form.get('force', 'false').lower() == 'true'
conn = get_connection() conn = get_connection()
cur = conn.cursor() cur = conn.cursor()
@@ -185,21 +188,77 @@ def delete_license(license_id):
flash('Lizenz nicht gefunden!', 'error') flash('Lizenz nicht gefunden!', 'error')
return redirect(url_for('licenses.licenses')) return redirect(url_for('licenses.licenses'))
# Safety check: Don't delete active licenses unless forced
if license_data.get('is_active') and not force_delete:
flash(f'Lizenz {license_data["license_key"]} ist noch aktiv! Bitte deaktivieren Sie die Lizenz zuerst oder nutzen Sie "Erzwungenes Löschen".', 'warning')
return redirect(url_for('licenses.licenses'))
# Check for recent activity (heartbeats in last 24 hours)
try:
cur.execute("""
SELECT COUNT(*)
FROM license_heartbeats
WHERE license_id = %s
AND timestamp > NOW() - INTERVAL '24 hours'
""", (license_id,))
recent_heartbeats = cur.fetchone()[0]
if recent_heartbeats > 0 and not force_delete:
flash(f'Lizenz {license_data["license_key"]} hatte in den letzten 24 Stunden {recent_heartbeats} Aktivitäten! '
f'Die Lizenz wird möglicherweise noch aktiv genutzt. Bitte prüfen Sie dies vor dem Löschen.', 'danger')
return redirect(url_for('licenses.licenses'))
except Exception as e:
# If heartbeats table doesn't exist, continue
logging.warning(f"Could not check heartbeats: {str(e)}")
# Check for active devices/activations
try:
cur.execute("""
SELECT COUNT(*)
FROM activations
WHERE license_id = %s
AND is_active = true
""", (license_id,))
active_devices = cur.fetchone()[0]
if active_devices > 0 and not force_delete:
flash(f'Lizenz {license_data["license_key"]} hat {active_devices} aktive Geräte! '
f'Bitte deaktivieren Sie alle Geräte vor dem Löschen.', 'danger')
return redirect(url_for('licenses.licenses'))
except Exception as e:
# If activations table doesn't exist, continue
logging.warning(f"Could not check activations: {str(e)}")
# Delete from sessions first # Delete from sessions first
cur.execute("DELETE FROM sessions WHERE license_key = %s", (license_data['license_key'],)) cur.execute("DELETE FROM sessions WHERE license_key = %s", (license_data['license_key'],))
# Delete from license_heartbeats if exists
try:
cur.execute("DELETE FROM license_heartbeats WHERE license_id = %s", (license_id,))
except:
pass
# Delete from activations if exists
try:
cur.execute("DELETE FROM activations WHERE license_id = %s", (license_id,))
except:
pass
# Delete the license # Delete the license
cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,)) cur.execute("DELETE FROM licenses WHERE id = %s", (license_id,))
conn.commit() conn.commit()
# Log deletion # Log deletion with force flag
log_audit('DELETE', 'license', license_id, log_audit('DELETE', 'license', license_id,
old_values={ old_values={
'license_key': license_data['license_key'], 'license_key': license_data['license_key'],
'customer_name': license_data['customer_name'], 'customer_name': license_data['customer_name'],
'customer_email': license_data['customer_email'] 'customer_email': license_data['customer_email'],
}) 'was_active': license_data.get('is_active'),
'forced': force_delete
},
additional_info=f"{'Forced deletion' if force_delete else 'Normal deletion'}")
flash(f'Lizenz {license_data["license_key"]} erfolgreich gelöscht!', 'success') flash(f'Lizenz {license_data["license_key"]} erfolgreich gelöscht!', 'success')

Datei anzeigen

@@ -367,9 +367,18 @@ function performBulkAction(url, ids) {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
// Show warnings if any licenses were skipped
if (data.warnings && data.warnings.length > 0) {
let warningMessage = data.message + '\n\n';
warningMessage += 'Warnungen:\n';
data.warnings.forEach(warning => {
warningMessage += '⚠️ ' + warning + '\n';
});
alert(warningMessage);
}
location.reload(); location.reload();
} else { } else {
alert('Fehler bei der Bulk-Aktion: ' + data.message); alert('Fehler bei der Bulk-Aktion: ' + (data.error || data.message));
} }
}); });
} }