homelab_automation/app/crud/docker_alert.py
Bruno Charest 68a9b0f390
Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
Remove Node.js cache files containing npm vulnerability data for vitest and vite packages
2025-12-15 20:36:06 -05:00

166 lines
5.6 KiB
Python

"""CRUD operations for Docker alerts."""
from datetime import datetime
from typing import List, Optional
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.docker_alert import DockerAlert
class DockerAlertRepository:
"""Repository for Docker alert CRUD operations."""
def __init__(self, session: AsyncSession):
self.session = session
async def get(self, alert_id: int) -> Optional[DockerAlert]:
"""Get an alert by its ID."""
result = await self.session.execute(
select(DockerAlert).where(DockerAlert.id == alert_id)
)
return result.scalar_one_or_none()
async def get_open_alert(self, host_id: str, container_name: str) -> Optional[DockerAlert]:
"""Get an open alert for a specific container."""
result = await self.session.execute(
select(DockerAlert).where(
DockerAlert.host_id == host_id,
DockerAlert.container_name == container_name,
DockerAlert.state == "open"
)
)
return result.scalar_one_or_none()
async def list_alerts(
self,
host_id: Optional[str] = None,
state: Optional[str] = None,
severity: Optional[str] = None,
limit: int = 100,
offset: int = 0
) -> List[DockerAlert]:
"""List alerts with optional filters."""
query = select(DockerAlert)
if host_id:
query = query.where(DockerAlert.host_id == host_id)
if state:
query = query.where(DockerAlert.state == state)
if severity:
query = query.where(DockerAlert.severity == severity)
query = query.order_by(DockerAlert.opened_at.desc()).limit(limit).offset(offset)
result = await self.session.execute(query)
return list(result.scalars().all())
async def count_alerts(
self,
host_id: Optional[str] = None,
state: Optional[str] = None
) -> dict:
"""Count alerts by state."""
base_query = select(DockerAlert)
if host_id:
base_query = base_query.where(DockerAlert.host_id == host_id)
# Count open
open_query = select(func.count(DockerAlert.id)).where(DockerAlert.state == "open")
if host_id:
open_query = open_query.where(DockerAlert.host_id == host_id)
# Count acknowledged
ack_query = select(func.count(DockerAlert.id)).where(DockerAlert.state == "acknowledged")
if host_id:
ack_query = ack_query.where(DockerAlert.host_id == host_id)
# Count total
total_query = select(func.count(DockerAlert.id))
if host_id:
total_query = total_query.where(DockerAlert.host_id == host_id)
open_result = await self.session.execute(open_query)
ack_result = await self.session.execute(ack_query)
total_result = await self.session.execute(total_query)
return {
"total": total_result.scalar() or 0,
"open": open_result.scalar() or 0,
"acknowledged": ack_result.scalar() or 0
}
async def count_open_by_host(self, host_id: str) -> int:
"""Count open alerts for a host."""
result = await self.session.execute(
select(func.count(DockerAlert.id)).where(
DockerAlert.host_id == host_id,
DockerAlert.state == "open"
)
)
return result.scalar() or 0
async def create(
self,
host_id: str,
container_name: str,
severity: str = "warning",
message: Optional[str] = None
) -> DockerAlert:
"""Create a new alert."""
alert = DockerAlert(
host_id=host_id,
container_name=container_name,
severity=severity,
state="open",
message=message,
opened_at=datetime.utcnow()
)
self.session.add(alert)
return alert
async def acknowledge(
self,
alert_id: int,
acknowledged_by: str
) -> Optional[DockerAlert]:
"""Acknowledge an alert."""
alert = await self.get(alert_id)
if alert and alert.state == "open":
alert.state = "acknowledged"
alert.acknowledged_at = datetime.utcnow()
alert.acknowledged_by = acknowledged_by
return alert
return None
async def close(self, alert_id: int) -> Optional[DockerAlert]:
"""Close an alert."""
alert = await self.get(alert_id)
if alert and alert.state in ("open", "acknowledged"):
alert.state = "closed"
alert.closed_at = datetime.utcnow()
return alert
return None
async def close_for_container(self, host_id: str, container_name: str) -> int:
"""Close all open alerts for a container."""
result = await self.session.execute(
select(DockerAlert).where(
DockerAlert.host_id == host_id,
DockerAlert.container_name == container_name,
DockerAlert.state.in_(["open", "acknowledged"])
)
)
alerts = result.scalars().all()
count = 0
for alert in alerts:
alert.state = "closed"
alert.closed_at = datetime.utcnow()
count += 1
return count
async def update_last_notified(self, alert_id: int) -> None:
"""Update the last notified timestamp."""
alert = await self.get(alert_id)
if alert:
alert.last_notified_at = datetime.utcnow()