diff --git a/decnet/web/db/repository.py b/decnet/web/db/repository.py index ecca4a1..944b73b 100644 --- a/decnet/web/db/repository.py +++ b/decnet/web/db/repository.py @@ -133,11 +133,12 @@ class BaseRepository(ABC): offset: int = 0, search: Optional[str] = None, sort_by: str = "recent", + service: Optional[str] = None, ) -> list[dict[str, Any]]: """Retrieve paginated attacker profile records.""" pass @abstractmethod - async def get_total_attackers(self, search: Optional[str] = None) -> int: + async def get_total_attackers(self, search: Optional[str] = None, service: Optional[str] = None) -> int: """Retrieve the total count of attacker profile records, optionally filtered.""" pass diff --git a/decnet/web/db/sqlite/repository.py b/decnet/web/db/sqlite/repository.py index db6bd3f..c58747d 100644 --- a/decnet/web/db/sqlite/repository.py +++ b/decnet/web/db/sqlite/repository.py @@ -484,6 +484,7 @@ class SQLiteRepository(BaseRepository): offset: int = 0, search: Optional[str] = None, sort_by: str = "recent", + service: Optional[str] = None, ) -> List[dict[str, Any]]: order = { "active": desc(Attacker.event_count), @@ -493,6 +494,8 @@ class SQLiteRepository(BaseRepository): statement = select(Attacker).order_by(order).offset(offset).limit(limit) if search: statement = statement.where(Attacker.ip.like(f"%{search}%")) + if service: + statement = statement.where(Attacker.services.like(f'%"{service}"%')) async with self.session_factory() as session: result = await session.execute(statement) @@ -501,10 +504,12 @@ class SQLiteRepository(BaseRepository): for a in result.scalars().all() ] - async def get_total_attackers(self, search: Optional[str] = None) -> int: + async def get_total_attackers(self, search: Optional[str] = None, service: Optional[str] = None) -> int: statement = select(func.count()).select_from(Attacker) if search: statement = statement.where(Attacker.ip.like(f"%{search}%")) + if service: + statement = statement.where(Attacker.services.like(f'%"{service}"%')) async with self.session_factory() as session: result = await session.execute(statement) diff --git a/decnet/web/router/attackers/api_get_attackers.py b/decnet/web/router/attackers/api_get_attackers.py index aa3fa07..0b33994 100644 --- a/decnet/web/router/attackers/api_get_attackers.py +++ b/decnet/web/router/attackers/api_get_attackers.py @@ -22,6 +22,7 @@ async def get_attackers( offset: int = Query(0, ge=0, le=2147483647), search: Optional[str] = None, sort_by: str = Query("recent", pattern="^(recent|active|traversals)$"), + service: Optional[str] = None, current_user: str = Depends(get_current_user), ) -> dict[str, Any]: """Retrieve paginated attacker profiles.""" @@ -31,6 +32,7 @@ async def get_attackers( return v s = _norm(search) - _data = await repo.get_attackers(limit=limit, offset=offset, search=s, sort_by=sort_by) - _total = await repo.get_total_attackers(search=s) + svc = _norm(service) + _data = await repo.get_attackers(limit=limit, offset=offset, search=s, sort_by=sort_by, service=svc) + _total = await repo.get_total_attackers(search=s, service=svc) return {"total": _total, "limit": limit, "offset": offset, "data": _data} diff --git a/decnet_web/src/components/AttackerDetail.tsx b/decnet_web/src/components/AttackerDetail.tsx index 098ffff..394845e 100644 --- a/decnet_web/src/components/AttackerDetail.tsx +++ b/decnet_web/src/components/AttackerDetail.tsx @@ -331,7 +331,13 @@ const AttackerDetail: React.FC = () => {