150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import List, Optional
|
|
|
|
from django.contrib.auth import get_user_model
|
|
from django.db import models
|
|
from django.db.models import Count
|
|
from django.http import HttpRequest
|
|
from ninja import Query, Router, Schema
|
|
from ninja.errors import HttpError
|
|
from pydantic import Field
|
|
|
|
from apps.core.rbac import require_perms
|
|
from apps.servers.models import Server
|
|
from apps.telemetry.models import TelemetryEvent
|
|
|
|
|
|
class TelemetryCreateIn(Schema):
|
|
event_type: str
|
|
server_id: Optional[int] = None
|
|
user_id: Optional[int] = None
|
|
success: bool = True
|
|
source: Optional[str] = None
|
|
message: Optional[str] = None
|
|
metadata: dict = Field(default_factory=dict)
|
|
|
|
|
|
class TelemetryOut(Schema):
|
|
id: int
|
|
event_type: str
|
|
server_id: Optional[int] = None
|
|
user_id: Optional[int] = None
|
|
success: bool
|
|
source: str
|
|
message: str
|
|
metadata: dict
|
|
created_at: str
|
|
|
|
|
|
class TelemetrySummaryOut(Schema):
|
|
total: int
|
|
success: int
|
|
failure: int
|
|
|
|
|
|
class TelemetryQuery(Schema):
|
|
limit: int = Field(default=50, ge=1, le=200)
|
|
offset: int = Field(default=0, ge=0)
|
|
event_type: Optional[str] = None
|
|
server_id: Optional[int] = None
|
|
user_id: Optional[int] = None
|
|
success: Optional[bool] = None
|
|
|
|
|
|
def _event_to_out(event: TelemetryEvent) -> TelemetryOut:
|
|
return TelemetryOut(
|
|
id=event.id,
|
|
event_type=event.event_type,
|
|
server_id=event.server_id,
|
|
user_id=event.user_id,
|
|
success=event.success,
|
|
source=event.source,
|
|
message=event.message or "",
|
|
metadata=event.metadata or {},
|
|
created_at=event.created_at.isoformat(),
|
|
)
|
|
|
|
|
|
def build_router() -> Router:
|
|
router = Router()
|
|
|
|
@router.get("/", response=List[TelemetryOut])
|
|
def list_events(request: HttpRequest, filters: TelemetryQuery = Query(...)):
|
|
"""List telemetry events emitted by the platform and agents.
|
|
|
|
Auth: required.
|
|
Permissions: requires `telemetry.view_telemetryevent`.
|
|
Filters: event_type, server_id, user_id, success.
|
|
Rationale: supports operational dashboards and audit-style timelines.
|
|
"""
|
|
require_perms(request, "telemetry.view_telemetryevent")
|
|
qs = TelemetryEvent.objects.order_by("-created_at")
|
|
if filters.event_type:
|
|
qs = qs.filter(event_type=filters.event_type)
|
|
if filters.server_id:
|
|
qs = qs.filter(server_id=filters.server_id)
|
|
if filters.user_id:
|
|
qs = qs.filter(user_id=filters.user_id)
|
|
if filters.success is not None:
|
|
qs = qs.filter(success=filters.success)
|
|
qs = qs[filters.offset : filters.offset + filters.limit]
|
|
return [_event_to_out(event) for event in qs]
|
|
|
|
@router.post("/", response=TelemetryOut)
|
|
def create_event(request: HttpRequest, payload: TelemetryCreateIn):
|
|
"""Create a telemetry event entry.
|
|
|
|
Auth: required.
|
|
Permissions: requires `telemetry.add_telemetryevent`.
|
|
Behavior: validates server/user references and normalizes source.
|
|
Rationale: used by internal automation; if external clients are not
|
|
expected to emit telemetry, this endpoint can be restricted further.
|
|
"""
|
|
require_perms(request, "telemetry.add_telemetryevent")
|
|
server = None
|
|
if payload.server_id:
|
|
try:
|
|
server = Server.objects.get(id=payload.server_id)
|
|
except Server.DoesNotExist:
|
|
raise HttpError(404, "Server not found")
|
|
if payload.user_id:
|
|
User = get_user_model()
|
|
if not User.objects.filter(id=payload.user_id).exists():
|
|
raise HttpError(404, "User not found")
|
|
source = payload.source or TelemetryEvent.Source.API
|
|
if source not in TelemetryEvent.Source.values:
|
|
raise HttpError(422, {"source": ["Invalid source."]})
|
|
event = TelemetryEvent.objects.create(
|
|
event_type=payload.event_type.strip(),
|
|
server=server,
|
|
user_id=payload.user_id,
|
|
success=payload.success,
|
|
source=source,
|
|
message=(payload.message or "").strip(),
|
|
metadata=payload.metadata or {},
|
|
)
|
|
return _event_to_out(event)
|
|
|
|
@router.get("/summary", response=TelemetrySummaryOut)
|
|
def summary(request: HttpRequest):
|
|
"""Return a high-level success/failure summary.
|
|
|
|
Auth: required.
|
|
Permissions: requires `telemetry.view_telemetryevent`.
|
|
Rationale: feeds dashboard widgets without pulling full event lists.
|
|
"""
|
|
require_perms(request, "telemetry.view_telemetryevent")
|
|
totals = TelemetryEvent.objects.aggregate(
|
|
total=Count("id"),
|
|
success=Count("id", filter=models.Q(success=True)),
|
|
)
|
|
total = totals.get("total") or 0
|
|
success = totals.get("success") or 0
|
|
return TelemetrySummaryOut(total=total, success=success, failure=total - success)
|
|
|
|
return router
|
|
|
|
|
|
router = build_router()
|