""" Enhanced API endpoints for logs with compression and file support """ from flask import Blueprint, request, jsonify from werkzeug.utils import secure_filename import os import hashlib from datetime import datetime from app.services.log_service import LogCompressionService from app.services.file_service import FileUploadService from app.models import Device, LogEntry, FileUpload from config.database_config import get_db import logging # Create blueprint logs_bp = Blueprint('logs', __name__, url_prefix='/api/logs') # Initialize services log_service = LogCompressionService() file_service = FileUploadService() @logs_bp.route('/', methods=['POST']) @logs_bp.route('/submit', methods=['POST']) def submit_log(): """ Enhanced log submission with compression support Expected JSON: { "hostname": "device-01", "device_ip": "192.168.1.100", "nume_masa": "Masa-01", "log_message": "Card detected: ABC123", "severity": "info", # optional: debug, info, warning, error, critical "source_file": "/path/to/logfile.log", # optional "metadata": {} # optional additional metadata } """ try: # Validate content type if not request.is_json: return jsonify({ 'error': 'Content-Type must be application/json', 'success': False }), 400 data = request.get_json() # Validate required fields required_fields = ['hostname', 'device_ip', 'nume_masa', 'log_message'] missing_fields = [field for field in required_fields if not data.get(field)] if missing_fields: return jsonify({ 'error': f'Missing required fields: {", ".join(missing_fields)}', 'success': False }), 400 # Prepare device info device_info = { 'hostname': data['hostname'], 'device_ip': data['device_ip'], 'nume_masa': data['nume_masa'], # Optional – clients can send these to keep device records up to date 'device_type': data.get('device_type') or (data.get('metadata') or {}).get('device_type'), 'os_version': data.get('os_version') or (data.get('metadata') or {}).get('os_version'), 'location': data.get('location') or (data.get('metadata') or {}).get('location'), 'mac_address': data.get('mac_address') or (data.get('metadata') or {}).get('mac_address'), } # Process log with compression result = log_service.process_log_message( device_info=device_info, message=data['log_message'], severity=data.get('severity', 'info') ) if result['success']: # Prepare response with compression info response_data = { 'success': True, 'message': 'Log processed successfully', 'log_id': result['log_id'], 'device_id': result['device_id'], 'compression_info': result['compression'] } # Add alias info if template was used if result['compression'].get('used_template'): response_data['template_alias'] = result['compression']['template_alias'] # For clients: suggest using alias in future requests if result['compression'].get('new_template'): response_data['suggestion'] = f"For similar messages, you can use template alias: {result['compression']['template_alias']}" return jsonify(response_data), 201 else: return jsonify(result), 500 except Exception as e: logging.error(f"Error in submit_log: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500 @logs_bp.route('/template/', methods=['POST']) def submit_templated_log(): """ Submit log using template alias (smaller payload) Expected JSON: { "alias": "CD001", "variables": {"card_id": "ABC123"}, "device_info": { "hostname": "device-01", "device_ip": "192.168.1.100", "nume_masa": "Masa-01" } } """ try: data = request.get_json() alias = request.view_args['alias'] # Validate required fields if not data.get('device_info'): return jsonify({ 'error': 'device_info is required', 'success': False }), 400 # Get template message variables = data.get('variables', {}) full_message = log_service.get_message_by_alias(alias, variables) if not full_message: return jsonify({ 'error': f'Template alias {alias} not found', 'success': False }), 404 # Process as regular log result = log_service.process_log_message( device_info=data['device_info'], message=full_message, severity=data.get('severity', 'info') ) return jsonify(result), 201 if result['success'] else 500 except Exception as e: logging.error(f"Error in submit_templated_log: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500 @logs_bp.route('/file', methods=['POST']) def upload_log_file(): """ Upload log file for processing Expects multipart/form-data with: - file: log file - device_info: JSON string with device information """ try: # Check if file was uploaded if 'file' not in request.files: return jsonify({ 'error': 'No file uploaded', 'success': False }), 400 file = request.files['file'] if file.filename == '': return jsonify({ 'error': 'No file selected', 'success': False }), 400 # Get device info device_info_str = request.form.get('device_info') if not device_info_str: return jsonify({ 'error': 'device_info is required', 'success': False }), 400 import json device_info = json.loads(device_info_str) # Process file upload result = file_service.process_uploaded_file(file, device_info) return jsonify(result), 201 if result['success'] else 500 except Exception as e: logging.error(f"Error in upload_log_file: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500 @logs_bp.route('/query', methods=['GET']) def query_logs(): """ Query logs with filters and pagination Query parameters: - device_id: Filter by device ID - hostname: Filter by hostname - severity: Filter by severity level - start_time: Start time (ISO format) - end_time: End time (ISO format) - limit: Number of results (default 100) - offset: Offset for pagination (default 0) - include_template: Include resolved template messages (default true) """ try: with get_db().get_session() as session: # Build query query = session.query(LogEntry).join(Device) # Apply filters if request.args.get('device_id'): query = query.filter(LogEntry.device_id == int(request.args.get('device_id'))) if request.args.get('hostname'): query = query.filter(Device.hostname == request.args.get('hostname')) if request.args.get('severity'): query = query.filter(LogEntry.severity == request.args.get('severity')) if request.args.get('start_time'): start_time = datetime.fromisoformat(request.args.get('start_time')) query = query.filter(LogEntry.timestamp >= start_time) if request.args.get('end_time'): end_time = datetime.fromisoformat(request.args.get('end_time')) query = query.filter(LogEntry.timestamp <= end_time) # Pagination limit = min(int(request.args.get('limit', 100)), 1000) # Max 1000 offset = int(request.args.get('offset', 0)) # Order by timestamp descending query = query.order_by(LogEntry.timestamp.desc()) # Get total count total_count = query.count() # Apply pagination logs = query.limit(limit).offset(offset).all() # Format response include_template = request.args.get('include_template', 'true').lower() == 'true' log_data = [] for log in logs: log_item = { 'id': log.id, 'device': { 'id': log.device.id, 'hostname': log.device.hostname, 'device_ip': log.device.device_ip, 'nume_masa': log.device.nume_masa }, 'timestamp': log.timestamp.isoformat(), 'severity': log.severity } if include_template and log.template: log_item['message'] = log.resolved_message log_item['template_alias'] = log.template.alias log_item['template_category'] = log.template.category else: log_item['message'] = log.full_message or log.resolved_message log_data.append(log_item) return jsonify({ 'success': True, 'logs': log_data, 'pagination': { 'total_count': total_count, 'limit': limit, 'offset': offset, 'has_more': offset + limit < total_count } }) except Exception as e: logging.error(f"Error in query_logs: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500 @logs_bp.route('/stats', methods=['GET']) def get_log_stats(): """Get logging and compression statistics""" try: # Get compression stats compression_stats = log_service.get_compression_stats() # Get additional stats with get_db().get_session() as session: # Device stats active_devices = session.query(Device).filter_by(status='active').count() total_devices = session.query(Device).count() # Recent activity from datetime import datetime, timedelta last_hour = datetime.utcnow() - timedelta(hours=1) recent_logs = session.query(LogEntry).filter( LogEntry.timestamp >= last_hour ).count() stats = { 'success': True, 'compression': compression_stats, 'devices': { 'active': active_devices, 'total': total_devices }, 'activity': { 'logs_last_hour': recent_logs } } return jsonify(stats) except Exception as e: logging.error(f"Error in get_log_stats: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500 @logs_bp.route('/templates', methods=['GET']) def get_templates(): """Get available message templates and aliases""" try: with get_db().get_session() as session: from app.models import MessageTemplate templates = session.query(MessageTemplate).order_by( MessageTemplate.usage_count.desc() ).all() template_data = [{ 'alias': template.alias, 'category': template.category, 'template_text': template.template_text, 'usage_count': template.usage_count, 'created_at': template.created_at.isoformat() } for template in templates] return jsonify({ 'success': True, 'templates': template_data, 'total_count': len(template_data) }) except Exception as e: logging.error(f"Error in get_templates: {e}") return jsonify({ 'error': 'Internal server error', 'success': False }), 500