diff --git a/v2_adminpanel/app.py b/v2_adminpanel/app.py index e39be6d..7aff548 100644 --- a/v2_adminpanel/app.py +++ b/v2_adminpanel/app.py @@ -2162,5 +2162,153 @@ def clear_attempts(): return redirect(url_for('blocked_ips')) +# API Endpoints for License Management +@app.route("/api/license//toggle", methods=["POST"]) +@login_required +def toggle_license_api(license_id): + """Toggle license active status via API""" + try: + data = request.get_json() + is_active = data.get('is_active', False) + + conn = get_connection() + cur = conn.cursor() + + # Update license status + cur.execute(""" + UPDATE licenses + SET is_active = %s + WHERE id = %s + """, (is_active, license_id)) + + conn.commit() + + # Log the action + log_audit('UPDATE', 'license', license_id, + new_values={'is_active': is_active}, + additional_info=f"Lizenz {'aktiviert' if is_active else 'deaktiviert'} via Toggle") + + cur.close() + conn.close() + + return jsonify({'success': True, 'message': 'Status erfolgreich geändert'}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}), 500 + +@app.route("/api/licenses/bulk-activate", methods=["POST"]) +@login_required +def bulk_activate_licenses(): + """Activate multiple licenses at once""" + try: + data = request.get_json() + license_ids = data.get('ids', []) + + if not license_ids: + return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400 + + conn = get_connection() + cur = conn.cursor() + + # Update all selected licenses + cur.execute(""" + UPDATE licenses + SET is_active = TRUE + WHERE id = ANY(%s) + """, (license_ids,)) + + affected_rows = cur.rowcount + conn.commit() + + # Log the bulk action + log_audit('BULK_UPDATE', 'licenses', None, + new_values={'is_active': True, 'count': affected_rows}, + additional_info=f"{affected_rows} Lizenzen aktiviert") + + cur.close() + conn.close() + + return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen aktiviert'}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}), 500 + +@app.route("/api/licenses/bulk-deactivate", methods=["POST"]) +@login_required +def bulk_deactivate_licenses(): + """Deactivate multiple licenses at once""" + try: + data = request.get_json() + license_ids = data.get('ids', []) + + if not license_ids: + return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400 + + conn = get_connection() + cur = conn.cursor() + + # Update all selected licenses + cur.execute(""" + UPDATE licenses + SET is_active = FALSE + WHERE id = ANY(%s) + """, (license_ids,)) + + affected_rows = cur.rowcount + conn.commit() + + # Log the bulk action + log_audit('BULK_UPDATE', 'licenses', None, + new_values={'is_active': False, 'count': affected_rows}, + additional_info=f"{affected_rows} Lizenzen deaktiviert") + + cur.close() + conn.close() + + return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen deaktiviert'}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}), 500 + +@app.route("/api/licenses/bulk-delete", methods=["POST"]) +@login_required +def bulk_delete_licenses(): + """Delete multiple licenses at once""" + try: + data = request.get_json() + license_ids = data.get('ids', []) + + if not license_ids: + return jsonify({'success': False, 'message': 'Keine Lizenzen ausgewählt'}), 400 + + conn = get_connection() + cur = conn.cursor() + + # Get license info for audit log + cur.execute(""" + SELECT license_key + FROM licenses + WHERE id = ANY(%s) + """, (license_ids,)) + license_keys = [row[0] for row in cur.fetchall()] + + # Delete all selected licenses + cur.execute(""" + DELETE FROM licenses + WHERE id = ANY(%s) + """, (license_ids,)) + + affected_rows = cur.rowcount + conn.commit() + + # Log the bulk action + log_audit('BULK_DELETE', 'licenses', None, + old_values={'license_keys': license_keys, 'count': affected_rows}, + additional_info=f"{affected_rows} Lizenzen gelöscht") + + cur.close() + conn.close() + + return jsonify({'success': True, 'message': f'{affected_rows} Lizenzen gelöscht'}) + except Exception as e: + return jsonify({'success': False, 'message': str(e)}), 500 + if __name__ == "__main__": app.run(host="0.0.0.0", port=5000) diff --git a/v2_adminpanel/sample_data.sql b/v2_adminpanel/sample_data.sql new file mode 100644 index 0000000..ec13968 --- /dev/null +++ b/v2_adminpanel/sample_data.sql @@ -0,0 +1,93 @@ +-- Beispieldaten für v2-Docker Admin Panel +-- Führen Sie dieses Script aus, um Testdaten zu generieren + +-- Kunden einfügen +INSERT INTO customers (name, email) VALUES + ('TechStart GmbH', 'info@techstart.de'), + ('Digital Solutions AG', 'kontakt@digital-solutions.ch'), + ('WebMaster Pro', 'admin@webmaster-pro.com'), + ('Social Media Experts', 'hello@social-experts.de'), + ('Marketing Genius Ltd', 'contact@marketing-genius.co.uk'), + ('StartUp Factory', 'team@startup-factory.de'), + ('Innovation Hub GmbH', 'info@innovation-hub.de'), + ('Creative Agency Berlin', 'office@creative-berlin.de'), + ('Data Analytics Corp', 'support@data-analytics.com'), + ('Cloud Services 24/7', 'info@cloud247.de'), + ('Mobile First Solutions', 'contact@mobile-first.de'), + ('AI Powered Marketing', 'hello@ai-marketing.de'), + ('Performance Media Group', 'info@performance-media.de'), + ('Growth Hacker Studio', 'team@growth-hacker.de'), + ('Digital Transformation AG', 'contact@digital-transform.ch') +ON CONFLICT (email) DO NOTHING; + +-- Lizenzen einfügen (verschiedene Status) +-- Aktive Vollversionen +INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active) VALUES + ('AF-202506F-A7K9-M3P2-X8R4', 1, 'full', CURRENT_DATE - INTERVAL '30 days', CURRENT_DATE + INTERVAL '335 days', true), + ('AF-202506F-B2N5-K8L3-Q9W7', 2, 'full', CURRENT_DATE - INTERVAL '60 days', CURRENT_DATE + INTERVAL '305 days', true), + ('AF-202506F-C4M8-P2R6-T5Y3', 3, 'full', CURRENT_DATE - INTERVAL '90 days', CURRENT_DATE + INTERVAL '275 days', true), + ('AF-202506F-D9L2-S7K4-U8N6', 4, 'full', CURRENT_DATE - INTERVAL '15 days', CURRENT_DATE + INTERVAL '350 days', true), + ('AF-202506F-E3P7-W5M9-V2X8', 5, 'full', CURRENT_DATE - INTERVAL '45 days', CURRENT_DATE + INTERVAL '320 days', true); + +-- Bald ablaufende Lizenzen (innerhalb 30 Tage) +INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active) VALUES + ('AF-202506F-F6K2-Y8L4-Z3N9', 6, 'full', CURRENT_DATE - INTERVAL '350 days', CURRENT_DATE + INTERVAL '15 days', true), + ('AF-202506F-G4M7-A9P3-B5R8', 7, 'full', CURRENT_DATE - INTERVAL '340 days', CURRENT_DATE + INTERVAL '25 days', true), + ('AF-202506T-H8N3-C2K6-D7L9', 8, 'test', CURRENT_DATE - INTERVAL '25 days', CURRENT_DATE + INTERVAL '5 days', true), + ('AF-202506T-J5P8-E4M2-F9N7', 9, 'test', CURRENT_DATE - INTERVAL '20 days', CURRENT_DATE + INTERVAL '10 days', true); + +-- Abgelaufene Lizenzen +INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active) VALUES + ('AF-202406F-K3L7-G8P4-H2M9', 10, 'full', CURRENT_DATE - INTERVAL '400 days', CURRENT_DATE - INTERVAL '35 days', true), + ('AF-202406F-L9N2-J5K8-M7P3', 11, 'full', CURRENT_DATE - INTERVAL '380 days', CURRENT_DATE - INTERVAL '15 days', true), + ('AF-202406T-M4K6-L3N9-P8R2', 12, 'test', CURRENT_DATE - INTERVAL '45 days', CURRENT_DATE - INTERVAL '15 days', true); + +-- Deaktivierte Lizenzen +INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active) VALUES + ('AF-202506F-N7P3-Q2L8-R5K4', 13, 'full', CURRENT_DATE - INTERVAL '120 days', CURRENT_DATE + INTERVAL '245 days', false), + ('AF-202506F-P8M5-S4N7-T9L2', 14, 'full', CURRENT_DATE - INTERVAL '180 days', CURRENT_DATE + INTERVAL '185 days', false), + ('AF-202506T-Q3K9-U7P5-V2M8', 15, 'test', CURRENT_DATE - INTERVAL '10 days', CURRENT_DATE + INTERVAL '20 days', false); + +-- Testversionen +INSERT INTO licenses (license_key, customer_id, license_type, valid_from, valid_until, is_active) VALUES + ('AF-202506T-R6N4-W8L2-X3P7', 1, 'test', CURRENT_DATE, CURRENT_DATE + INTERVAL '30 days', true), + ('AF-202506T-S9K7-Y5M3-Z8N2', 3, 'test', CURRENT_DATE - INTERVAL '5 days', CURRENT_DATE + INTERVAL '25 days', true), + ('AF-202506T-T4L8-A2P6-B7K3', 5, 'test', CURRENT_DATE - INTERVAL '10 days', CURRENT_DATE + INTERVAL '20 days', true); + +-- Sessions einfügen (nur für Demonstration) +-- Aktive Sessions +INSERT INTO sessions (license_id, session_id, ip_address, user_agent, last_heartbeat, is_active) VALUES + (1, 'sess_' || gen_random_uuid(), '192.168.1.100', 'Mozilla/5.0 Windows NT 10.0', CURRENT_TIMESTAMP - INTERVAL '2 minutes', true), + (2, 'sess_' || gen_random_uuid(), '10.0.0.50', 'Mozilla/5.0 Macintosh', CURRENT_TIMESTAMP - INTERVAL '5 minutes', true), + (3, 'sess_' || gen_random_uuid(), '172.16.0.25', 'Chrome/91.0.4472.124', CURRENT_TIMESTAMP - INTERVAL '1 minute', true), + (4, 'sess_' || gen_random_uuid(), '192.168.2.75', 'Safari/14.1.1', CURRENT_TIMESTAMP - INTERVAL '8 minutes', true); + +-- Inaktive Sessions (beendet) +INSERT INTO sessions (license_id, session_id, ip_address, user_agent, started_at, last_heartbeat, ended_at, is_active) VALUES + (5, 'sess_' || gen_random_uuid(), '10.10.10.10', 'Firefox/89.0', CURRENT_TIMESTAMP - INTERVAL '2 hours', CURRENT_TIMESTAMP - INTERVAL '1 hour', CURRENT_TIMESTAMP - INTERVAL '1 hour', false), + (6, 'sess_' || gen_random_uuid(), '172.20.0.100', 'Edge/91.0.864.59', CURRENT_TIMESTAMP - INTERVAL '5 hours', CURRENT_TIMESTAMP - INTERVAL '3 hours', CURRENT_TIMESTAMP - INTERVAL '3 hours', false); + +-- Audit Log Einträge +INSERT INTO audit_log (username, action, entity_type, entity_id, new_values, ip_address, additional_info) VALUES + ('rac00n', 'CREATE', 'customer', 1, '{"name": "TechStart GmbH", "email": "info@techstart.de"}', '192.168.1.1', 'Neuer Kunde angelegt'), + ('w@rh@mm3r', 'CREATE', 'license', 1, '{"license_key": "AF-202506F-A7K9-M3P2-X8R4", "type": "full"}', '192.168.1.2', 'Vollversion erstellt'), + ('rac00n', 'UPDATE', 'license', 13, '{"is_active": false}', '192.168.1.1', 'Lizenz deaktiviert'), + ('w@rh@mm3r', 'DELETE', 'customer', 16, '{"name": "Test Kunde"}', '192.168.1.2', 'Kunde ohne Lizenzen gelöscht'), + ('rac00n', 'EXPORT', 'licenses', null, '{"format": "excel", "count": 15}', '192.168.1.1', 'Lizenzexport durchgeführt'), + ('system', 'CREATE_BATCH', 'licenses', null, '{"customer": "Digital Solutions AG", "count": 5}', '127.0.0.1', 'Batch-Lizenzen erstellt'); + +-- Login Attempts (für Security Dashboard) +INSERT INTO login_attempts (ip_address, attempt_count, last_username_tried, last_error_message) VALUES + ('192.168.100.50', 2, 'admin', 'Falsches Passwort'), + ('10.0.0.200', 3, 'test', 'Benutzer nicht gefunden'), + ('172.16.50.100', 1, 'rac00n', 'Falsches Passwort'); + +-- Ein gesperrter Versuch +INSERT INTO login_attempts (ip_address, attempt_count, last_username_tried, last_error_message, blocked_until) VALUES + ('192.168.200.100', 5, 'hacker', 'Zu viele Fehlversuche', CURRENT_TIMESTAMP + INTERVAL '20 hours'); + +-- Backup History +INSERT INTO backup_history (filename, filepath, filesize, backup_type, status, created_by, tables_count, records_count, duration_seconds, is_encrypted) VALUES + ('backup_v2docker_20250608_120000_encrypted.sql.gz.enc', '/app/backups/backup_v2docker_20250608_120000_encrypted.sql.gz.enc', 1048576, 'manual', 'success', 'rac00n', 8, 150, 2.5, true), + ('backup_v2docker_20250608_030000_encrypted.sql.gz.enc', '/app/backups/backup_v2docker_20250608_030000_encrypted.sql.gz.enc', 1024000, 'scheduled', 'success', 'system', 8, 145, 2.1, true), + ('backup_v2docker_20250607_150000_encrypted.sql.gz.enc', '/app/backups/backup_v2docker_20250607_150000_encrypted.sql.gz.enc', 980000, 'manual', 'success', 'w@rh@mm3r', 8, 140, 1.9, true); \ No newline at end of file diff --git a/v2_adminpanel/templates/base.html b/v2_adminpanel/templates/base.html index e619b6a..fac00a1 100644 --- a/v2_adminpanel/templates/base.html +++ b/v2_adminpanel/templates/base.html @@ -80,6 +80,88 @@ z-index: 9999; max-width: 400px; } + + /* Table Improvements */ + .table-container { + max-height: 600px; + overflow-y: auto; + position: relative; + } + + .table-sticky thead { + position: sticky; + top: 0; + background-color: #fff; + z-index: 10; + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.1); + } + + .table-sticky thead th { + background-color: #f8f9fa; + border-bottom: 2px solid #dee2e6; + } + + /* Inline Actions */ + .btn-copy { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.2s; + } + + .btn-copy:hover { + background-color: #e9ecef; + } + + .btn-copy.copied { + background-color: var(--status-active); + color: white; + } + + /* Toggle Switch */ + .form-switch-custom { + display: inline-block; + } + + .form-switch-custom .form-check-input { + cursor: pointer; + width: 3em; + height: 1.5em; + } + + .form-switch-custom .form-check-input:checked { + background-color: var(--status-active); + border-color: var(--status-active); + } + + /* Bulk Actions Bar */ + .bulk-actions { + position: sticky; + bottom: 0; + background-color: #212529; + color: white; + padding: 1rem; + display: none; + z-index: 100; + box-shadow: 0 -4px 6px rgba(0, 0, 0, 0.1); + } + + .bulk-actions.show { + display: flex; + align-items: center; + justify-content: space-between; + } + + /* Checkbox Styling */ + .checkbox-cell { + width: 40px; + } + + .form-check-input-custom { + cursor: pointer; + width: 1.2em; + height: 1.2em; + } diff --git a/v2_adminpanel/templates/dashboard.html b/v2_adminpanel/templates/dashboard.html index 7028ac5..eaccc73 100644 --- a/v2_adminpanel/templates/dashboard.html +++ b/v2_adminpanel/templates/dashboard.html @@ -49,13 +49,6 @@ animation: pulse 2s infinite; } - /* Chart styles */ - .chart-container { - position: relative; - height: 100px; - margin: 1rem 0; - } - /* Progress bar styles */ .progress-custom { height: 8px; @@ -101,9 +94,6 @@
📋
{{ stats.total_licenses }}
Lizenzen Gesamt
-
- -
@@ -348,46 +338,4 @@ -{% endblock %} - -{% block extra_js %} - - {% endblock %} \ No newline at end of file diff --git a/v2_adminpanel/templates/licenses.html b/v2_adminpanel/templates/licenses.html index cf2ceb4..ade7346 100644 --- a/v2_adminpanel/templates/licenses.html +++ b/v2_adminpanel/templates/licenses.html @@ -74,11 +74,14 @@
-
-
- +
+
+
+ @@ -94,8 +97,18 @@ {% for license in licenses %} + - +
+ + ID Lizenzschlüssel Kunde
+ + {{ license[0] }}{{ license[1] }} +
+ {{ license[1] }} + +
+
{{ license[2] }} {{ license[3] or '-' }} @@ -117,11 +130,12 @@ {% endif %} - {% if license[7] %} - - {% else %} - - {% endif %} +
+ +
@@ -190,4 +204,129 @@
+ + +
+
+ 0 Lizenzen ausgewählt +
+
+ + + +
+
+{% endblock %} + +{% block extra_js %} + {% endblock %} \ No newline at end of file