feat: phase 1C JWT UI, events/hosts API, Vue frontend
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Query
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
|
||||
from app.auth.api_key import get_api_key_auth
|
||||
from app.auth.jwt_auth import get_current_user
|
||||
from app.config import get_settings
|
||||
from app.database import get_db
|
||||
from app.models import Event, Host
|
||||
from app.schemas.list_models import EventDetail, EventListResponse, EventSummary
|
||||
from app.services.ingest import ingest_event
|
||||
from app.services.schema_validate import validate_event_payload
|
||||
|
||||
@@ -41,3 +46,109 @@ def post_event(
|
||||
created=created,
|
||||
sac_event_url=f"{base}/api/v1/events/{event.id}",
|
||||
)
|
||||
|
||||
|
||||
def _parse_optional_dt(value: str | None) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
|
||||
|
||||
@router.get("", response_model=EventListResponse)
|
||||
def list_events(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=200),
|
||||
severity: str | None = None,
|
||||
type: str | None = None,
|
||||
host_id: int | None = None,
|
||||
hostname: str | None = None,
|
||||
from_time: str | None = Query(None, alias="from"),
|
||||
to_time: str | None = Query(None, alias="to"),
|
||||
q: str | None = None,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EventListResponse:
|
||||
stmt = select(Event).join(Host).options(joinedload(Event.host))
|
||||
count_stmt = select(func.count()).select_from(Event).join(Host)
|
||||
|
||||
if severity:
|
||||
stmt = stmt.where(Event.severity == severity)
|
||||
count_stmt = count_stmt.where(Event.severity == severity)
|
||||
if type:
|
||||
stmt = stmt.where(Event.type == type)
|
||||
count_stmt = count_stmt.where(Event.type == type)
|
||||
if host_id is not None:
|
||||
stmt = stmt.where(Event.host_id == host_id)
|
||||
count_stmt = count_stmt.where(Event.host_id == host_id)
|
||||
if hostname:
|
||||
like = f"%{hostname}%"
|
||||
stmt = stmt.where(Host.hostname.ilike(like))
|
||||
count_stmt = count_stmt.where(Host.hostname.ilike(like))
|
||||
dt_from = _parse_optional_dt(from_time)
|
||||
dt_to = _parse_optional_dt(to_time)
|
||||
if dt_from:
|
||||
stmt = stmt.where(Event.occurred_at >= dt_from)
|
||||
count_stmt = count_stmt.where(Event.occurred_at >= dt_from)
|
||||
if dt_to:
|
||||
stmt = stmt.where(Event.occurred_at <= dt_to)
|
||||
count_stmt = count_stmt.where(Event.occurred_at <= dt_to)
|
||||
if q:
|
||||
like = f"%{q}%"
|
||||
stmt = stmt.where(Event.summary.ilike(like) | Event.title.ilike(like))
|
||||
count_stmt = count_stmt.where(Event.summary.ilike(like) | Event.title.ilike(like))
|
||||
|
||||
total = db.scalar(count_stmt) or 0
|
||||
rows = db.scalars(
|
||||
stmt.order_by(Event.occurred_at.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
).all()
|
||||
|
||||
items = [
|
||||
EventSummary(
|
||||
id=e.id,
|
||||
event_id=e.event_id,
|
||||
host_id=e.host_id,
|
||||
hostname=e.host.hostname,
|
||||
occurred_at=e.occurred_at,
|
||||
received_at=e.received_at,
|
||||
category=e.category,
|
||||
type=e.type,
|
||||
severity=e.severity,
|
||||
title=e.title,
|
||||
summary=e.summary,
|
||||
)
|
||||
for e in rows
|
||||
]
|
||||
return EventListResponse(items=items, total=total, page=page, page_size=page_size)
|
||||
|
||||
|
||||
@router.get("/{event_db_id}", response_model=EventDetail)
|
||||
def get_event(
|
||||
event_db_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EventDetail:
|
||||
event = db.scalar(
|
||||
select(Event).where(Event.id == event_db_id).options(joinedload(Event.host))
|
||||
)
|
||||
if event is None:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
return EventDetail(
|
||||
id=event.id,
|
||||
event_id=event.event_id,
|
||||
host_id=event.host_id,
|
||||
hostname=event.host.hostname,
|
||||
occurred_at=event.occurred_at,
|
||||
received_at=event.received_at,
|
||||
category=event.category,
|
||||
type=event.type,
|
||||
severity=event.severity,
|
||||
title=event.title,
|
||||
summary=event.summary,
|
||||
details=event.details,
|
||||
raw=event.raw,
|
||||
dedup_key=event.dedup_key,
|
||||
correlation_id=event.correlation_id,
|
||||
payload=event.payload,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user