108e1756c4
Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session, joinedload
|
|
|
|
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, Problem
|
|
from app.schemas.list_models import EventSummary
|
|
from app.services.host_health import DAILY_REPORT_SSH, HEARTBEAT_TYPE, count_stale_hosts
|
|
|
|
router = APIRouter(prefix="/dashboards", tags=["dashboards"])
|
|
|
|
|
|
def fetch_recent_events(db: Session, *, limit: int = 8) -> list[EventSummary]:
|
|
"""Последние события по времени приёма ingest (received_at)."""
|
|
rows = db.scalars(
|
|
select(Event)
|
|
.join(Host)
|
|
.options(joinedload(Event.host))
|
|
.order_by(Event.received_at.desc())
|
|
.limit(limit)
|
|
).all()
|
|
return [
|
|
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
|
|
]
|
|
|
|
|
|
class TopHostItem(BaseModel):
|
|
host_id: int
|
|
hostname: str
|
|
count: int
|
|
|
|
|
|
class TopTypeItem(BaseModel):
|
|
type: str
|
|
count: int
|
|
|
|
|
|
class DashboardSummary(BaseModel):
|
|
events_last_24h: int
|
|
hosts_total: int
|
|
hosts_stale: int
|
|
heartbeats_24h: int
|
|
daily_reports_24h: int
|
|
problems_open: int
|
|
problems_opened_24h: int
|
|
problems_resolved_24h: int
|
|
severity_24h: dict[str, int]
|
|
top_hosts: list[TopHostItem]
|
|
top_event_types: list[TopTypeItem]
|
|
recent_events: list[EventSummary]
|
|
|
|
|
|
@router.get("/summary", response_model=DashboardSummary)
|
|
def dashboard_summary(
|
|
db: Session = Depends(get_db),
|
|
_user: str = Depends(get_current_user),
|
|
) -> DashboardSummary:
|
|
since = datetime.now(timezone.utc) - timedelta(hours=24)
|
|
|
|
events_24h = db.scalar(
|
|
select(func.count()).select_from(Event).where(Event.received_at >= since)
|
|
) or 0
|
|
hosts_total = db.scalar(select(func.count()).select_from(Host)) or 0
|
|
settings = get_settings()
|
|
hosts_stale = count_stale_hosts(db, settings.sac_heartbeat_stale_minutes)
|
|
heartbeats_24h = db.scalar(
|
|
select(func.count())
|
|
.select_from(Event)
|
|
.where(Event.received_at >= since, Event.type == HEARTBEAT_TYPE)
|
|
) or 0
|
|
daily_reports_24h = db.scalar(
|
|
select(func.count())
|
|
.select_from(Event)
|
|
.where(Event.received_at >= since, Event.type == DAILY_REPORT_SSH)
|
|
) or 0
|
|
problems_open = (
|
|
db.scalar(select(func.count()).select_from(Problem).where(Problem.status == "open")) or 0
|
|
)
|
|
problems_opened_24h = (
|
|
db.scalar(select(func.count()).select_from(Problem).where(Problem.created_at >= since)) or 0
|
|
)
|
|
problems_resolved_24h = (
|
|
db.scalar(
|
|
select(func.count()).select_from(Problem).where(
|
|
Problem.status == "resolved",
|
|
Problem.updated_at >= since,
|
|
)
|
|
)
|
|
or 0
|
|
)
|
|
|
|
top_host_rows = db.execute(
|
|
select(Host.id, Host.hostname, func.count())
|
|
.select_from(Event)
|
|
.join(Host, Event.host_id == Host.id)
|
|
.where(Event.received_at >= since)
|
|
.group_by(Host.id, Host.hostname)
|
|
.order_by(func.count().desc())
|
|
.limit(10)
|
|
).all()
|
|
top_hosts = [
|
|
TopHostItem(host_id=row[0], hostname=row[1], count=row[2]) for row in top_host_rows
|
|
]
|
|
|
|
top_type_rows = db.execute(
|
|
select(Event.type, func.count())
|
|
.where(Event.received_at >= since)
|
|
.group_by(Event.type)
|
|
.order_by(func.count().desc())
|
|
.limit(10)
|
|
).all()
|
|
top_event_types = [TopTypeItem(type=row[0], count=row[1]) for row in top_type_rows]
|
|
|
|
severity_rows = db.execute(
|
|
select(Event.severity, func.count())
|
|
.where(Event.received_at >= since)
|
|
.group_by(Event.severity)
|
|
).all()
|
|
severity_24h = {row[0]: row[1] for row in severity_rows}
|
|
|
|
recent_events = fetch_recent_events(db, limit=8)
|
|
|
|
return DashboardSummary(
|
|
events_last_24h=events_24h,
|
|
hosts_total=hosts_total,
|
|
hosts_stale=hosts_stale,
|
|
heartbeats_24h=heartbeats_24h,
|
|
daily_reports_24h=daily_reports_24h,
|
|
problems_open=problems_open,
|
|
problems_opened_24h=problems_opened_24h,
|
|
problems_resolved_24h=problems_resolved_24h,
|
|
severity_24h=severity_24h,
|
|
top_hosts=top_hosts,
|
|
top_event_types=top_event_types,
|
|
recent_events=recent_events,
|
|
)
|
|
|
|
|
|
@router.get("/recent-events", response_model=list[EventSummary])
|
|
def dashboard_recent_events(
|
|
limit: int = Query(8, ge=1, le=50),
|
|
db: Session = Depends(get_db),
|
|
_user: str = Depends(get_current_user),
|
|
) -> list[EventSummary]:
|
|
return fetch_recent_events(db, limit=limit)
|