diff --git a/backend/app/api/v1/dashboards.py b/backend/app/api/v1/dashboards.py new file mode 100644 index 0000000..fdf9194 --- /dev/null +++ b/backend/app/api/v1/dashboards.py @@ -0,0 +1,77 @@ +from datetime import datetime, timedelta, timezone + +from fastapi import APIRouter, Depends +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.database import get_db +from app.models import Event, Host, Problem +from app.schemas.list_models import EventSummary + +router = APIRouter(prefix="/dashboards", tags=["dashboards"]) + + +class DashboardSummary(BaseModel): + events_last_24h: int + hosts_total: int + problems_open: int + severity_24h: dict[str, int] + 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 + problems_open = ( + db.scalar(select(func.count()).select_from(Problem).where(Problem.status == "open")) or 0 + ) + + 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 = db.scalars( + select(Event) + .join(Host) + .options(joinedload(Event.host)) + .order_by(Event.received_at.desc()) + .limit(8) + ).all() + + recent_events = [ + 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 recent + ] + + return DashboardSummary( + events_last_24h=events_24h, + hosts_total=hosts_total, + problems_open=problems_open, + severity_24h=severity_24h, + recent_events=recent_events, + ) diff --git a/backend/app/api/v1/router.py b/backend/app/api/v1/router.py index 08fcab7..22bd2be 100644 --- a/backend/app/api/v1/router.py +++ b/backend/app/api/v1/router.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from app.api.v1 import auth, events, health, hosts, problems +from app.api.v1 import auth, dashboards, events, health, hosts, problems api_router = APIRouter() api_router.include_router(health.router) @@ -8,3 +8,4 @@ api_router.include_router(auth.router) api_router.include_router(events.router) api_router.include_router(hosts.router) api_router.include_router(problems.router) +api_router.include_router(dashboards.router) diff --git a/docs/work-plan.md b/docs/work-plan.md index 8d54048..711913f 100644 --- a/docs/work-plan.md +++ b/docs/work-plan.md @@ -24,8 +24,8 @@ | 1A.1 | Параметры `UseSAC`, `SAC_URL`, `SAC_API_KEY`, spool | ✅ ssh-monitor | | 1A.2 | `build_sac_event()` + `send_sac_event()` по schema v1 | ✅ `sac-client.sh` | | 1A.3 | `notify_or_sac()`: off / dual / exclusive / fallback | ✅ + маппинг событий | -| 1A.4 | `--check-sac` / `Test-SacConnection` | 🔄 ssh-monitor `--check-sac` | -| 1A.5 | README агентов | ⏳ | +| 1A.4 | `--check-sac` / `Test-SacConnection` | ✅ ssh-monitor `--check-sac` | +| 1A.5 | README агентов, first deploy | ✅ `first_deploy.sh`, `--deploy` | **Выход:** на тестовом хосте `dual` шлёт JSON в SAC + Telegram. @@ -52,8 +52,8 @@ | 1C.1 | Frontend Vue: Events, Hosts | ✅ prod | | 1C.2 | Auth JWT, admin bootstrap | ✅ prod | | 1C.3 | Problems (базовые правила) | ✅ API + UI (деплой ⏳) | -| 1C.4 | Telegram из SAC | 🔄 backend MVP (деплой ⏳) | -| 1C.5 | Dashboard (3 виджета), SSE | ⏳ | +| 1C.4 | Telegram из SAC | ✅ backend (деплой + TELEGRAM_* в env) | +| 1C.5 | Dashboard (3 виджета), SSE | 🔄 Dashboard MVP ✅, SSE ⏳ | **Выход:** тег `v0.1.0-mvp`. diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 5629146..a1ca970 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -2,6 +2,7 @@