from typing import List, Dict, Any from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from models import LogEntry class StatsAnalyzer: @staticmethod async def overview(session: AsyncSession) -> Dict[str, Any]: total = (await session.execute(select(func.count()).select_from(LogEntry))).scalar() or 0 fw = (await session.execute( select(func.count()).select_from(LogEntry).where(LogEntry.log_type == "firewall") )).scalar() or 0 px = (await session.execute( select(func.count()).select_from(LogEntry).where(LogEntry.log_type == "proxy") )).scalar() or 0 return { "total_entries": total, "firewall_entries": fw, "proxy_entries": px, } @staticmethod async def top_sources(session: AsyncSession, limit: int = 20) -> List[Dict[str, Any]]: stmt = ( select(LogEntry.source_ip, func.count().label("cnt")) .where(LogEntry.source_ip.isnot(None)) .group_by(LogEntry.source_ip) .order_by(func.count().desc()) .limit(limit) ) rows = await session.execute(stmt) return [{"source_ip": r[0], "count": r[1]} for r in rows] @staticmethod async def top_destinations(session: AsyncSession, limit: int = 20) -> List[Dict[str, Any]]: stmt = ( select(LogEntry.destination_ip, func.count().label("cnt")) .where(LogEntry.destination_ip.isnot(None)) .group_by(LogEntry.destination_ip) .order_by(func.count().desc()) .limit(limit) ) rows = await session.execute(stmt) return [{"destination_ip": r[0], "count": r[1]} for r in rows] @staticmethod async def top_ports(session: AsyncSession, limit: int = 20) -> List[Dict[str, Any]]: stmt = ( select(LogEntry.destination_port, func.count().label("cnt")) .where(LogEntry.destination_port.isnot(None)) .group_by(LogEntry.destination_port) .order_by(func.count().desc()) .limit(limit) ) rows = await session.execute(stmt) return [{"destination_port": r[0], "count": r[1]} for r in rows] @staticmethod async def top_urls(session: AsyncSession, limit: int = 20) -> List[Dict[str, Any]]: stmt = ( select(LogEntry.url, func.count().label("cnt")) .where(LogEntry.url.isnot(None)) .group_by(LogEntry.url) .order_by(func.count().desc()) .limit(limit) ) rows = await session.execute(stmt) return [{"url": r[0], "count": r[1]} for r in rows] @staticmethod async def action_distribution(session: AsyncSession) -> List[Dict[str, Any]]: stmt = ( select(LogEntry.action, func.count().label("cnt")) .where(LogEntry.action.isnot(None)) .group_by(LogEntry.action) .order_by(func.count().desc()) ) rows = await session.execute(stmt) return [{"action": r[0], "count": r[1]} for r in rows] @staticmethod async def timeline(session: AsyncSession, granularity: str = "hour") -> List[Dict[str, Any]]: if granularity == "hour": fmt = "%Y-%m-%d %H:00" else: fmt = "%Y-%m-%d" # SQLite strftime stmt = ( select(func.strftime(fmt, LogEntry.timestamp).label("bucket"), func.count().label("cnt")) .where(LogEntry.timestamp.isnot(None)) .group_by("bucket") .order_by("bucket") ) rows = await session.execute(stmt) return [{"time_bucket": r[0], "count": r[1]} for r in rows] @staticmethod async def unique_counts(session: AsyncSession) -> Dict[str, int]: src = (await session.execute( select(func.count(func.distinct(LogEntry.source_ip))) )).scalar() or 0 dst = (await session.execute( select(func.count(func.distinct(LogEntry.destination_ip))) )).scalar() or 0 return {"unique_sources": src, "unique_destinations": dst}