feat: hide event types from SAC UI via Settings checkbox (0.20.23)
Per-type «Показывать в событиях» in severity settings; filters lists, dashboard, hosts, and problems.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from pydantic import BaseModel
|
||||
@@ -11,6 +11,7 @@ from app.database import get_db
|
||||
from app.models import Event, Host, Problem
|
||||
from app.schemas.list_models import EventSummary
|
||||
from app.services.event_summary import event_to_summary
|
||||
from app.services.event_type_visibility import get_hidden_event_types, visibility_type_filter
|
||||
from app.services.host_health import DAILY_REPORT_TYPES, HEARTBEAT_TYPE, count_stale_hosts
|
||||
|
||||
router = APIRouter(prefix="/dashboards", tags=["dashboards"])
|
||||
@@ -18,13 +19,18 @@ router = APIRouter(prefix="/dashboards", tags=["dashboards"])
|
||||
|
||||
def fetch_recent_events(db: Session, *, limit: int = 8) -> list[EventSummary]:
|
||||
"""Последние события по времени приёма ingest (received_at)."""
|
||||
rows = db.scalars(
|
||||
hidden = get_hidden_event_types(db)
|
||||
type_filter = visibility_type_filter(hidden)
|
||||
stmt = (
|
||||
select(Event)
|
||||
.join(Host)
|
||||
.options(joinedload(Event.host))
|
||||
.order_by(Event.received_at.desc())
|
||||
.limit(limit)
|
||||
).all()
|
||||
)
|
||||
if type_filter is not None:
|
||||
stmt = stmt.where(type_filter)
|
||||
rows = db.scalars(stmt).all()
|
||||
return [event_to_summary(e, db) for e in rows]
|
||||
|
||||
|
||||
@@ -61,10 +67,13 @@ def dashboard_summary(
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> DashboardSummary:
|
||||
since = datetime.now(timezone.utc) - timedelta(hours=24)
|
||||
hidden = get_hidden_event_types(db)
|
||||
type_filter = visibility_type_filter(hidden)
|
||||
|
||||
events_24h = db.scalar(
|
||||
select(func.count()).select_from(Event).where(Event.received_at >= since)
|
||||
) or 0
|
||||
events_24h_stmt = select(func.count()).select_from(Event).where(Event.received_at >= since)
|
||||
if type_filter is not None:
|
||||
events_24h_stmt = events_24h_stmt.where(type_filter)
|
||||
events_24h = db.scalar(events_24h_stmt) 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)
|
||||
@@ -100,12 +109,16 @@ def dashboard_summary(
|
||||
or 0
|
||||
)
|
||||
|
||||
top_host_rows = db.execute(
|
||||
top_host_stmt = (
|
||||
select(Host.id, Host.hostname, Host.display_name, func.count())
|
||||
.select_from(Event)
|
||||
.join(Host, Event.host_id == Host.id)
|
||||
.where(Event.received_at >= since)
|
||||
.group_by(Host.id, Host.hostname, Host.display_name)
|
||||
)
|
||||
if type_filter is not None:
|
||||
top_host_stmt = top_host_stmt.where(type_filter)
|
||||
top_host_rows = db.execute(
|
||||
top_host_stmt.group_by(Host.id, Host.hostname, Host.display_name)
|
||||
.order_by(func.count().desc())
|
||||
.limit(10)
|
||||
).all()
|
||||
@@ -113,20 +126,18 @@ def dashboard_summary(
|
||||
TopHostItem(host_id=row[0], hostname=row[1], display_name=row[2], count=row[3]) for row in top_host_rows
|
||||
]
|
||||
|
||||
top_type_stmt = select(Event.type, func.count()).where(Event.received_at >= since)
|
||||
if type_filter is not None:
|
||||
top_type_stmt = top_type_stmt.where(type_filter)
|
||||
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)
|
||||
top_type_stmt.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_stmt = select(Event.severity, func.count()).where(Event.received_at >= since)
|
||||
if type_filter is not None:
|
||||
severity_stmt = severity_stmt.where(type_filter)
|
||||
severity_rows = db.execute(severity_stmt.group_by(Event.severity)).all()
|
||||
severity_24h = {row[0]: row[1] for row in severity_rows}
|
||||
|
||||
recent_events = fetch_recent_events(db, limit=8)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import logging
|
||||
import logging
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
@@ -22,6 +22,7 @@ from app.services.agent_commands import (
|
||||
from app.services.rdg_winrm_actions import execute_logoff_via_winrm, execute_qwinsta_via_winrm
|
||||
from app.services.ingest import ingest_event
|
||||
from app.services.event_summary import event_to_summary
|
||||
from app.services.event_type_visibility import get_hidden_event_types, visibility_type_filter
|
||||
from app.services.problems import maybe_create_problem
|
||||
from app.services.schema_validate import validate_event_payload
|
||||
from app.services.notify_dispatch import (
|
||||
@@ -134,8 +135,13 @@ def list_events(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EventListResponse:
|
||||
hidden = get_hidden_event_types(db)
|
||||
type_filter = visibility_type_filter(hidden)
|
||||
stmt = select(Event).join(Host).options(joinedload(Event.host))
|
||||
count_stmt = select(func.count()).select_from(Event).join(Host)
|
||||
if type_filter is not None:
|
||||
stmt = stmt.where(type_filter)
|
||||
count_stmt = count_stmt.where(type_filter)
|
||||
|
||||
if severity:
|
||||
stmt = stmt.where(Event.severity == severity)
|
||||
@@ -261,11 +267,14 @@ def get_event(
|
||||
db: Session = Depends(get_db),
|
||||
_user: str = Depends(get_current_user),
|
||||
) -> EventDetail:
|
||||
hidden = get_hidden_event_types(db)
|
||||
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")
|
||||
if event.type in hidden:
|
||||
raise HTTPException(status_code=404, detail="Event not found")
|
||||
base = event_to_summary(event, db)
|
||||
return EventDetail(
|
||||
**base.model_dump(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -56,10 +56,19 @@ from app.services.host_health import (
|
||||
max_daily_report_time_by_host,
|
||||
max_event_time_by_host,
|
||||
)
|
||||
from app.services.event_type_visibility import get_hidden_event_types, visibility_type_filter
|
||||
|
||||
router = APIRouter(prefix="/hosts", tags=["hosts"])
|
||||
|
||||
|
||||
def _count_visible_events(db: Session, host_id: int, hidden: frozenset[str]) -> int:
|
||||
stmt = select(func.count()).select_from(Event).where(Event.host_id == host_id)
|
||||
type_filter = visibility_type_filter(hidden)
|
||||
if type_filter is not None:
|
||||
stmt = stmt.where(type_filter)
|
||||
return int(db.scalar(stmt) or 0)
|
||||
|
||||
|
||||
@router.get("", response_model=HostListResponse)
|
||||
def list_hosts(
|
||||
page: int = Query(1, ge=1),
|
||||
@@ -108,12 +117,11 @@ def list_hosts(
|
||||
latest_map,
|
||||
git_versions=git_release.versions,
|
||||
)
|
||||
hidden = get_hidden_event_types(db)
|
||||
|
||||
items: list[HostSummary] = []
|
||||
for host in rows:
|
||||
event_count = db.scalar(
|
||||
select(func.count()).select_from(Event).where(Event.host_id == host.id)
|
||||
)
|
||||
event_count = _count_visible_events(db, host.id, hidden)
|
||||
last_hb = hb_map.get(host.id)
|
||||
items.append(
|
||||
HostSummary(
|
||||
@@ -161,9 +169,8 @@ def _host_detail_from_model(
|
||||
) -> HostDetail:
|
||||
hb_map = max_event_time_by_host(db, HEARTBEAT_TYPE)
|
||||
report_map = max_daily_report_time_by_host(db)
|
||||
event_count = db.scalar(
|
||||
select(func.count()).select_from(Event).where(Event.host_id == host.id)
|
||||
)
|
||||
hidden = get_hidden_event_types(db)
|
||||
event_count = _count_visible_events(db, host.id, hidden)
|
||||
last_hb = hb_map.get(host.id)
|
||||
latest_map = latest_agent_versions_by_product(db)
|
||||
update_cfg = get_effective_agent_update_config(db)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from app.auth.jwt_auth import get_current_user
|
||||
from app.database import get_db
|
||||
|
||||
from app.models import Event, Host, Problem, ProblemEvent
|
||||
from app.services.event_type_visibility import get_hidden_event_types
|
||||
|
||||
|
||||
|
||||
@@ -269,6 +270,8 @@ def get_problem(
|
||||
.order_by(Event.occurred_at.desc())
|
||||
|
||||
).all()
|
||||
hidden = get_hidden_event_types(db)
|
||||
event_rows = [event for event in event_rows if event.type not in hidden]
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from dataclasses import replace
|
||||
from dataclasses import replace
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
@@ -37,6 +37,7 @@ from app.services.event_severity_overrides import (
|
||||
list_severity_override_items,
|
||||
replace_severity_overrides,
|
||||
)
|
||||
from app.services.event_type_visibility import replace_event_visibility
|
||||
from app.services.ui_settings import (
|
||||
get_effective_ui_settings,
|
||||
upsert_ui_settings,
|
||||
@@ -375,6 +376,7 @@ class EventSeverityOverrideRow(BaseModel):
|
||||
event_type: str
|
||||
default_severity: str
|
||||
override_severity: str | None = None
|
||||
show_in_events: bool = True
|
||||
|
||||
|
||||
class EventSeverityOverridesResponse(BaseModel):
|
||||
@@ -384,8 +386,25 @@ class EventSeverityOverridesResponse(BaseModel):
|
||||
|
||||
class EventSeverityOverridesUpdate(BaseModel):
|
||||
overrides: dict[str, str | None] = Field(
|
||||
default_factory=dict,
|
||||
description="event_type → severity; null или пустая строка сбрасывает override",
|
||||
)
|
||||
visibility: dict[str, bool | None] = Field(
|
||||
default_factory=dict,
|
||||
description="event_type → show_in_events; null или true сбрасывает скрытие (показывать)",
|
||||
)
|
||||
|
||||
|
||||
def _severity_override_rows(items) -> list[EventSeverityOverrideRow]:
|
||||
return [
|
||||
EventSeverityOverrideRow(
|
||||
event_type=i.event_type,
|
||||
default_severity=i.default_severity,
|
||||
override_severity=i.override_severity,
|
||||
show_in_events=i.show_in_events,
|
||||
)
|
||||
for i in items
|
||||
]
|
||||
|
||||
|
||||
@router.get("/notifications/severity-overrides", response_model=EventSeverityOverridesResponse)
|
||||
@@ -395,14 +414,7 @@ def get_severity_overrides(
|
||||
) -> EventSeverityOverridesResponse:
|
||||
items = list_severity_override_items(db)
|
||||
return EventSeverityOverridesResponse(
|
||||
items=[
|
||||
EventSeverityOverrideRow(
|
||||
event_type=i.event_type,
|
||||
default_severity=i.default_severity,
|
||||
override_severity=i.override_severity,
|
||||
)
|
||||
for i in items
|
||||
],
|
||||
items=_severity_override_rows(items),
|
||||
source=SOURCE_DB,
|
||||
)
|
||||
|
||||
@@ -414,19 +426,19 @@ def update_severity_overrides(
|
||||
_user=Depends(require_admin),
|
||||
) -> EventSeverityOverridesResponse:
|
||||
try:
|
||||
items = replace_severity_overrides(db, body.overrides)
|
||||
items = list_severity_override_items(db)
|
||||
if body.overrides:
|
||||
items = replace_severity_overrides(db, body.overrides)
|
||||
if body.visibility:
|
||||
replace_event_visibility(db, body.visibility)
|
||||
items = list_severity_override_items(db)
|
||||
if not body.overrides and not body.visibility:
|
||||
raise HTTPException(status_code=422, detail="Нет изменений для сохранения")
|
||||
db.commit()
|
||||
except ValueError as exc:
|
||||
raise HTTPException(status_code=422, detail=str(exc)) from exc
|
||||
return EventSeverityOverridesResponse(
|
||||
items=[
|
||||
EventSeverityOverrideRow(
|
||||
event_type=i.event_type,
|
||||
default_severity=i.default_severity,
|
||||
override_severity=i.override_severity,
|
||||
)
|
||||
for i in items
|
||||
],
|
||||
items=_severity_override_rows(items),
|
||||
source=SOURCE_DB,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user