Remove Node.js cache files containing npm vulnerability data for vitest and vite packages
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
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
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
"""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()
|
||||
@@ -0,0 +1,140 @@
|
||||
"""CRUD operations for Docker containers."""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import case, delete, func, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.docker_container import DockerContainer
|
||||
|
||||
|
||||
class DockerContainerRepository:
|
||||
"""Repository for Docker container CRUD operations."""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def get(self, container_db_id: int) -> Optional[DockerContainer]:
|
||||
"""Get a container by its database ID."""
|
||||
result = await self.session.execute(
|
||||
select(DockerContainer).where(DockerContainer.id == container_db_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_container_id(self, host_id: str, container_id: str) -> Optional[DockerContainer]:
|
||||
"""Get a container by host ID and Docker container ID."""
|
||||
result = await self.session.execute(
|
||||
select(DockerContainer).where(
|
||||
DockerContainer.host_id == host_id,
|
||||
DockerContainer.container_id == container_id
|
||||
).limit(1)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_by_name(self, host_id: str, name: str) -> Optional[DockerContainer]:
|
||||
"""Get a container by host ID and container name."""
|
||||
result = await self.session.execute(
|
||||
select(DockerContainer).where(
|
||||
DockerContainer.host_id == host_id,
|
||||
DockerContainer.name == name
|
||||
).limit(1)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
||||
async def list_by_host(
|
||||
self,
|
||||
host_id: str,
|
||||
state: Optional[str] = None,
|
||||
compose_project: Optional[str] = None
|
||||
) -> List[DockerContainer]:
|
||||
"""List all containers for a host with optional filters."""
|
||||
query = select(DockerContainer).where(DockerContainer.host_id == host_id)
|
||||
|
||||
if state:
|
||||
query = query.where(DockerContainer.state == state)
|
||||
if compose_project:
|
||||
query = query.where(DockerContainer.compose_project == compose_project)
|
||||
|
||||
query = query.order_by(DockerContainer.name)
|
||||
result = await self.session.execute(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def count_by_host(self, host_id: str) -> dict:
|
||||
"""Count containers by state for a host."""
|
||||
result = await self.session.execute(
|
||||
select(
|
||||
func.count(DockerContainer.id).label('total'),
|
||||
func.sum(case((DockerContainer.state == 'running', 1), else_=0)).label('running')
|
||||
).where(DockerContainer.host_id == host_id)
|
||||
)
|
||||
row = result.one()
|
||||
return {
|
||||
"total": row.total or 0,
|
||||
"running": row.running or 0
|
||||
}
|
||||
|
||||
async def upsert(
|
||||
self,
|
||||
host_id: str,
|
||||
container_id: str,
|
||||
name: str,
|
||||
image: Optional[str] = None,
|
||||
state: str = "unknown",
|
||||
status: Optional[str] = None,
|
||||
health: Optional[str] = None,
|
||||
created_at: Optional[datetime] = None,
|
||||
ports: Optional[dict] = None,
|
||||
labels: Optional[dict] = None,
|
||||
compose_project: Optional[str] = None
|
||||
) -> DockerContainer:
|
||||
"""Create or update a container."""
|
||||
existing = await self.get_by_container_id(host_id, container_id)
|
||||
|
||||
if existing:
|
||||
existing.name = name
|
||||
existing.image = image
|
||||
existing.state = state
|
||||
existing.status = status
|
||||
existing.health = health
|
||||
existing.created_at = created_at
|
||||
existing.ports = ports
|
||||
existing.labels = labels
|
||||
existing.compose_project = compose_project
|
||||
existing.last_update_at = datetime.utcnow()
|
||||
return existing
|
||||
|
||||
container = DockerContainer(
|
||||
host_id=host_id,
|
||||
container_id=container_id,
|
||||
name=name,
|
||||
image=image,
|
||||
state=state,
|
||||
status=status,
|
||||
health=health,
|
||||
created_at=created_at,
|
||||
ports=ports,
|
||||
labels=labels,
|
||||
compose_project=compose_project
|
||||
)
|
||||
self.session.add(container)
|
||||
return container
|
||||
|
||||
async def delete_by_host(self, host_id: str) -> int:
|
||||
"""Delete all containers for a host."""
|
||||
result = await self.session.execute(
|
||||
delete(DockerContainer).where(DockerContainer.host_id == host_id)
|
||||
)
|
||||
return result.rowcount
|
||||
|
||||
async def delete_stale(self, host_id: str, current_container_ids: List[str]) -> int:
|
||||
"""Delete containers that no longer exist on the host."""
|
||||
if not current_container_ids:
|
||||
return await self.delete_by_host(host_id)
|
||||
|
||||
result = await self.session.execute(
|
||||
delete(DockerContainer).where(
|
||||
DockerContainer.host_id == host_id,
|
||||
DockerContainer.container_id.notin_(current_container_ids)
|
||||
)
|
||||
)
|
||||
return result.rowcount
|
||||
@@ -0,0 +1,103 @@
|
||||
"""CRUD operations for Docker images."""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import select, delete, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.docker_image import DockerImage
|
||||
|
||||
|
||||
class DockerImageRepository:
|
||||
"""Repository for Docker image CRUD operations."""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def get(self, image_db_id: int) -> Optional[DockerImage]:
|
||||
"""Get an image by its database ID."""
|
||||
result = await self.session.execute(
|
||||
select(DockerImage).where(DockerImage.id == image_db_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_image_id(self, host_id: str, image_id: str) -> Optional[DockerImage]:
|
||||
"""Get an image by host ID and Docker image ID."""
|
||||
result = await self.session.execute(
|
||||
select(DockerImage).where(
|
||||
DockerImage.host_id == host_id,
|
||||
DockerImage.image_id == image_id
|
||||
).limit(1)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
||||
async def list_by_host(self, host_id: str) -> List[DockerImage]:
|
||||
"""List all images for a host."""
|
||||
result = await self.session.execute(
|
||||
select(DockerImage)
|
||||
.where(DockerImage.host_id == host_id)
|
||||
.order_by(DockerImage.last_update_at.desc())
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def count_by_host(self, host_id: str) -> dict:
|
||||
"""Count images and total size for a host."""
|
||||
result = await self.session.execute(
|
||||
select(
|
||||
func.count(DockerImage.id).label('total'),
|
||||
func.coalesce(func.sum(DockerImage.size), 0).label('total_size')
|
||||
).where(DockerImage.host_id == host_id)
|
||||
)
|
||||
row = result.one()
|
||||
return {
|
||||
"total": row.total or 0,
|
||||
"total_size": row.total_size or 0
|
||||
}
|
||||
|
||||
async def upsert(
|
||||
self,
|
||||
host_id: str,
|
||||
image_id: str,
|
||||
repo_tags: Optional[List[str]] = None,
|
||||
size: Optional[int] = None,
|
||||
created: Optional[datetime] = None
|
||||
) -> DockerImage:
|
||||
"""Create or update an image."""
|
||||
existing = await self.get_by_image_id(host_id, image_id)
|
||||
|
||||
if existing:
|
||||
existing.repo_tags = repo_tags
|
||||
existing.size = size
|
||||
existing.created = created
|
||||
existing.last_update_at = datetime.utcnow()
|
||||
return existing
|
||||
|
||||
image = DockerImage(
|
||||
host_id=host_id,
|
||||
image_id=image_id,
|
||||
repo_tags=repo_tags,
|
||||
size=size,
|
||||
created=created
|
||||
)
|
||||
self.session.add(image)
|
||||
return image
|
||||
|
||||
async def delete_by_host(self, host_id: str) -> int:
|
||||
"""Delete all images for a host."""
|
||||
result = await self.session.execute(
|
||||
delete(DockerImage).where(DockerImage.host_id == host_id)
|
||||
)
|
||||
return result.rowcount
|
||||
|
||||
async def delete_stale(self, host_id: str, current_image_ids: List[str]) -> int:
|
||||
"""Delete images that no longer exist on the host."""
|
||||
if not current_image_ids:
|
||||
return await self.delete_by_host(host_id)
|
||||
|
||||
result = await self.session.execute(
|
||||
delete(DockerImage).where(
|
||||
DockerImage.host_id == host_id,
|
||||
DockerImage.image_id.notin_(current_image_ids)
|
||||
)
|
||||
)
|
||||
return result.rowcount
|
||||
@@ -0,0 +1,96 @@
|
||||
"""CRUD operations for Docker volumes."""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from sqlalchemy import select, delete, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.docker_volume import DockerVolume
|
||||
|
||||
|
||||
class DockerVolumeRepository:
|
||||
"""Repository for Docker volume CRUD operations."""
|
||||
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def get(self, volume_db_id: int) -> Optional[DockerVolume]:
|
||||
"""Get a volume by its database ID."""
|
||||
result = await self.session.execute(
|
||||
select(DockerVolume).where(DockerVolume.id == volume_db_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
async def get_by_name(self, host_id: str, name: str) -> Optional[DockerVolume]:
|
||||
"""Get a volume by host ID and volume name."""
|
||||
result = await self.session.execute(
|
||||
select(DockerVolume).where(
|
||||
DockerVolume.host_id == host_id,
|
||||
DockerVolume.name == name
|
||||
).limit(1)
|
||||
)
|
||||
return result.scalars().first()
|
||||
|
||||
async def list_by_host(self, host_id: str) -> List[DockerVolume]:
|
||||
"""List all volumes for a host."""
|
||||
result = await self.session.execute(
|
||||
select(DockerVolume)
|
||||
.where(DockerVolume.host_id == host_id)
|
||||
.order_by(DockerVolume.name)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def count_by_host(self, host_id: str) -> int:
|
||||
"""Count volumes for a host."""
|
||||
result = await self.session.execute(
|
||||
select(func.count(DockerVolume.id)).where(DockerVolume.host_id == host_id)
|
||||
)
|
||||
return result.scalar() or 0
|
||||
|
||||
async def upsert(
|
||||
self,
|
||||
host_id: str,
|
||||
name: str,
|
||||
driver: Optional[str] = None,
|
||||
mountpoint: Optional[str] = None,
|
||||
scope: Optional[str] = None
|
||||
) -> DockerVolume:
|
||||
"""Create or update a volume."""
|
||||
existing = await self.get_by_name(host_id, name)
|
||||
|
||||
if existing:
|
||||
existing.driver = driver
|
||||
existing.mountpoint = mountpoint
|
||||
existing.scope = scope
|
||||
existing.last_update_at = datetime.utcnow()
|
||||
return existing
|
||||
|
||||
volume = DockerVolume(
|
||||
host_id=host_id,
|
||||
name=name,
|
||||
driver=driver,
|
||||
mountpoint=mountpoint,
|
||||
scope=scope
|
||||
)
|
||||
self.session.add(volume)
|
||||
return volume
|
||||
|
||||
async def delete_by_host(self, host_id: str) -> int:
|
||||
"""Delete all volumes for a host."""
|
||||
result = await self.session.execute(
|
||||
delete(DockerVolume).where(DockerVolume.host_id == host_id)
|
||||
)
|
||||
return result.rowcount
|
||||
|
||||
async def delete_stale(self, host_id: str, current_volume_names: List[str]) -> int:
|
||||
"""Delete volumes that no longer exist on the host."""
|
||||
if not current_volume_names:
|
||||
return await self.delete_by_host(host_id)
|
||||
|
||||
result = await self.session.execute(
|
||||
delete(DockerVolume).where(
|
||||
DockerVolume.host_id == host_id,
|
||||
DockerVolume.name.notin_(current_volume_names)
|
||||
)
|
||||
)
|
||||
return result.rowcount
|
||||
@@ -72,3 +72,13 @@ class HostRepository:
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.rowcount > 0
|
||||
|
||||
async def list_docker_enabled(self) -> list[Host]:
|
||||
"""List all hosts with Docker monitoring enabled."""
|
||||
stmt = (
|
||||
select(Host)
|
||||
.where(Host.deleted_at.is_(None), Host.docker_enabled == True)
|
||||
.order_by(Host.name)
|
||||
)
|
||||
result = await self.session.execute(stmt)
|
||||
return result.scalars().all()
|
||||
|
||||
Reference in New Issue
Block a user