""" Device management service with CRUD operations and device monitoring """ import logging from typing import List, Optional, Dict, Any from datetime import datetime, timedelta from sqlalchemy.orm import Session, joinedload from sqlalchemy import desc, func, and_ from app.models import Device, LogEntry, FileUpload, InventoryGroup from config.database_config import get_db class DeviceService: """Service for managing devices and device-related operations""" def __init__(self): self.db = get_db() self.logger = logging.getLogger(__name__) # Basic CRUD Operations def create_device(self, hostname: str, device_ip: str, nume_masa: str, **kwargs) -> Device: """Create a new device""" try: with self.db.get_session() as session: # Check if device already exists existing = session.query(Device).filter( (Device.hostname == hostname) | (Device.device_ip == device_ip) ).first() if existing: raise ValueError(f"Device with hostname '{hostname}' or IP '{device_ip}' already exists") device = Device( hostname=hostname, device_ip=device_ip, nume_masa=nume_masa, device_type=kwargs.get('device_type', 'unknown'), os_version=kwargs.get('os_version'), status=kwargs.get('status', 'active'), location=kwargs.get('location'), description=kwargs.get('description'), last_seen=datetime.utcnow() ) session.add(device) session.commit() session.refresh(device) self.logger.info(f"Created device: {hostname} ({device_ip})") return device except Exception as e: self.logger.error(f"Error creating device: {e}") raise def get_device_by_id(self, device_id: int) -> Optional[Device]: """Get device by ID with relationships loaded""" try: with self.db.get_session() as session: return session.query(Device).options( joinedload(Device.logs), joinedload(Device.files), joinedload(Device.inventory_groups) ).filter(Device.id == device_id).first() except Exception as e: self.logger.error(f"Error getting device {device_id}: {e}") return None def get_device_by_hostname(self, hostname: str) -> Optional[Device]: """Get device by hostname""" try: with self.db.get_session() as session: return session.query(Device).filter(Device.hostname == hostname).first() except Exception as e: self.logger.error(f"Error getting device by hostname {hostname}: {e}") return None def get_device_by_ip(self, device_ip: str) -> Optional[Device]: """Get device by IP address""" try: with self.db.get_session() as session: return session.query(Device).filter(Device.device_ip == device_ip).first() except Exception as e: self.logger.error(f"Error getting device by IP {device_ip}: {e}") return None def get_all_devices(self, status: Optional[str] = None, limit: Optional[int] = None) -> List[Device]: """Get all devices with optional filtering""" try: with self.db.get_session() as session: query = session.query(Device).order_by(desc(Device.last_seen)) if status: query = query.filter(Device.status == status) if limit: query = query.limit(limit) return query.all() except Exception as e: self.logger.error(f"Error getting devices: {e}") return [] def update_device(self, device_id: int, **kwargs) -> Optional[Device]: """Update device information""" try: with self.db.get_session() as session: device = session.query(Device).filter(Device.id == device_id).first() if not device: return None # Update allowed fields allowed_fields = [ 'hostname', 'device_ip', 'nume_masa', 'device_type', 'os_version', 'status', 'location', 'description' ] for field, value in kwargs.items(): if field in allowed_fields and hasattr(device, field): setattr(device, field, value) session.commit() session.refresh(device) self.logger.info(f"Updated device {device_id}") return device except Exception as e: self.logger.error(f"Error updating device {device_id}: {e}") raise def delete_device(self, device_id: int) -> bool: """Delete device (soft delete by setting status to inactive)""" try: with self.db.get_session() as session: device = session.query(Device).filter(Device.id == device_id).first() if not device: return False # Soft delete - set status to inactive device.status = 'inactive' session.commit() self.logger.info(f"Soft deleted device {device_id}") return True except Exception as e: self.logger.error(f"Error deleting device {device_id}: {e}") return False # Device Monitoring Functions def update_device_last_seen(self, hostname: str = None, device_ip: str = None) -> Optional[Device]: """Update device last seen timestamp""" try: with self.db.get_session() as session: device = None if hostname: device = session.query(Device).filter(Device.hostname == hostname).first() elif device_ip: device = session.query(Device).filter(Device.device_ip == device_ip).first() if device: device.last_seen = datetime.utcnow() session.commit() return device except Exception as e: self.logger.error(f"Error updating last seen: {e}") return None def get_device_statistics(self, device_id: int) -> Dict[str, Any]: """Get comprehensive statistics for a device""" try: with self.db.get_session() as session: device = session.query(Device).filter(Device.id == device_id).first() if not device: return {} # Log statistics total_logs = session.query(LogEntry).filter(LogEntry.device_id == device_id).count() # Logs by severity severity_counts = session.query( LogEntry.severity, func.count(LogEntry.id) ).filter( LogEntry.device_id == device_id ).group_by(LogEntry.severity).all() # Recent activity (last 24 hours) last_24h = datetime.utcnow() - timedelta(hours=24) recent_logs = session.query(LogEntry).filter( and_(LogEntry.device_id == device_id, LogEntry.timestamp >= last_24h) ).count() # File uploads total_files = session.query(FileUpload).filter( FileUpload.device_id == device_id ).count() # Last log last_log = session.query(LogEntry).filter( LogEntry.device_id == device_id ).order_by(desc(LogEntry.timestamp)).first() return { 'device': device, 'total_logs': total_logs, 'severity_counts': dict(severity_counts), 'recent_logs_24h': recent_logs, 'total_files': total_files, 'last_log': last_log, 'uptime_days': (datetime.utcnow() - device.last_seen).days if device.last_seen else 0 } except Exception as e: self.logger.error(f"Error getting device statistics: {e}") return {} def get_inactive_devices(self, hours: int = 24) -> List[Device]: """Get devices that haven't been seen recently""" try: with self.db.get_session() as session: cutoff_time = datetime.utcnow() - timedelta(hours=hours) return session.query(Device).filter( and_( Device.last_seen < cutoff_time, Device.status.in_(['active', 'maintenance']) ) ).order_by(desc(Device.last_seen)).all() except Exception as e: self.logger.error(f"Error getting inactive devices: {e}") return [] def get_device_logs(self, device_id: int, limit: int = 100, severity: str = None) -> List[LogEntry]: """Get logs for a specific device""" try: with self.db.get_session() as session: query = session.query(LogEntry).filter( LogEntry.device_id == device_id ).order_by(desc(LogEntry.timestamp)) if severity: query = query.filter(LogEntry.severity == severity) return query.limit(limit).all() except Exception as e: self.logger.error(f"Error getting device logs: {e}") return [] def search_devices(self, search_term: str) -> List[Device]: """Search devices by hostname, IP, or description""" try: with self.db.get_session() as session: search_pattern = f"%{search_term}%" return session.query(Device).filter( (Device.hostname.like(search_pattern)) | (Device.device_ip.like(search_pattern)) | (Device.nume_masa.like(search_pattern)) | (Device.location.like(search_pattern)) | (Device.description.like(search_pattern)) ).order_by(desc(Device.last_seen)).all() except Exception as e: self.logger.error(f"Error searching devices: {e}") return [] # Bulk Operations def bulk_update_status(self, device_ids: List[int], status: str) -> int: """Update status for multiple devices""" try: with self.db.get_session() as session: updated = session.query(Device).filter( Device.id.in_(device_ids) ).update({Device.status: status}, synchronize_session=False) session.commit() self.logger.info(f"Updated status for {updated} devices") return updated except Exception as e: self.logger.error(f"Error bulk updating status: {e}") return 0 def get_device_summary(self) -> Dict[str, Any]: """Get summary statistics for all devices""" try: with self.db.get_session() as session: # Device status counts status_counts = session.query( Device.status, func.count(Device.id) ).group_by(Device.status).all() # Device type counts type_counts = session.query( Device.device_type, func.count(Device.id) ).group_by(Device.device_type).all() # Recent activity last_24h = datetime.utcnow() - timedelta(hours=24) devices_seen_24h = session.query(Device).filter( Device.last_seen >= last_24h ).count() return { 'total_devices': session.query(Device).count(), 'status_counts': dict(status_counts), 'type_counts': dict(type_counts), 'devices_seen_24h': devices_seen_24h } except Exception as e: self.logger.error(f"Error getting device summary: {e}") return {}