Löschen Lizenz Schutz
Dieser Commit ist enthalten in:
@@ -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:
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren