WIP
This commit is contained in:
parent
e70a0584c9
commit
a7a5b5c8ea
43 changed files with 5531 additions and 9 deletions
163
control_plane/views.py
Normal file
163
control_plane/views.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from django.db.models import Count
|
||||
from django.db.models import Prefetch
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.views.generic import TemplateView
|
||||
from django.views.generic import View
|
||||
|
||||
from control_plane.local_test_runtime import build_test_django_local_url
|
||||
from control_plane.models import Deployment
|
||||
from control_plane.models import DeploymentStatus
|
||||
from control_plane.models import RuntimeService
|
||||
from control_plane.observability import JsonValue
|
||||
from control_plane.observability import load_test_deployment_diagnostics
|
||||
from control_plane.observability import probe_test_deployment_health
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from django.db.models import QuerySet
|
||||
from django.http import HttpRequest
|
||||
|
||||
|
||||
type RouteKwarg = str | UUID
|
||||
type DashboardContextValue = int | tuple[DeploymentCard, ...] | tuple[DeploymentStatusSummary, ...]
|
||||
type DetailContextValue = (
|
||||
DashboardContextValue | Deployment | tuple[RuntimeService, ...] | str | dict[str, JsonValue] | None
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DeploymentStatusSummary:
|
||||
"""Aggregate deployments by lifecycle state for dashboard cards."""
|
||||
|
||||
status: str
|
||||
label: str
|
||||
total: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class DeploymentCard:
|
||||
"""Small view model used by the dashboard templates."""
|
||||
|
||||
deployment: Deployment
|
||||
sentinel_url: str
|
||||
runtime_services: tuple[RuntimeService, ...]
|
||||
|
||||
@property
|
||||
def runtime_ready_total(self) -> int:
|
||||
"""Return total runtime services currently marked ready."""
|
||||
return sum(runtime_service.status == "ready" for runtime_service in self.runtime_services)
|
||||
|
||||
@property
|
||||
def runtime_failed_total(self) -> int:
|
||||
"""Return total runtime services currently marked failed."""
|
||||
return sum(runtime_service.status == "failed" for runtime_service in self.runtime_services)
|
||||
|
||||
|
||||
def _deployment_queryset() -> QuerySet[Deployment]:
|
||||
runtime_services = RuntimeService.objects.order_by("kind")
|
||||
return Deployment.objects.select_related("hosted_site__tenant").prefetch_related(
|
||||
Prefetch("runtime_services", queryset=runtime_services),
|
||||
)
|
||||
|
||||
|
||||
class DeploymentDashboardView(TemplateView):
|
||||
"""Render recent test deployments with links to diagnostics and sentinel probes."""
|
||||
|
||||
template_name = "control_plane/deployment_dashboard.html"
|
||||
|
||||
def get_context_data(self, **kwargs: RouteKwarg) -> dict[str, DashboardContextValue]:
|
||||
"""Build recent deployment cards plus aggregate status counts for the dashboard.
|
||||
|
||||
Returns:
|
||||
Template context containing deployment cards and summary counters.
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
deployments = tuple(_deployment_queryset().order_by("-created_at")[:24])
|
||||
context.update(
|
||||
{
|
||||
"deployment_cards": tuple(_build_deployment_card(deployment) for deployment in deployments),
|
||||
"status_summaries": _build_status_summaries(),
|
||||
"running_total": sum(deployment.status == DeploymentStatus.RUNNING.value for deployment in deployments),
|
||||
"deployment_total": len(deployments),
|
||||
},
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class DeploymentDetailView(TemplateView):
|
||||
"""Render one deployment with persisted diagnostics, logs, and live health state."""
|
||||
|
||||
template_name = "control_plane/deployment_detail.html"
|
||||
|
||||
def get_context_data(self, **kwargs: RouteKwarg) -> dict[str, DetailContextValue]:
|
||||
"""Build one deployment view with persisted diagnostics and an initial health probe.
|
||||
|
||||
Returns:
|
||||
Template context containing deployment metadata, diagnostics, and health state.
|
||||
"""
|
||||
context: dict[str, Any] = super().get_context_data(**kwargs)
|
||||
deployment: Deployment = get_object_or_404(_deployment_queryset(), pk=self.kwargs["deployment_id"])
|
||||
runtime_services = tuple(deployment.runtime_services.all())
|
||||
context.update(
|
||||
{
|
||||
"deployment": deployment,
|
||||
"runtime_services": runtime_services,
|
||||
"sentinel_url": build_test_django_local_url(deployment),
|
||||
"diagnostics": load_test_deployment_diagnostics(deployment),
|
||||
"health_probe": probe_test_deployment_health(deployment),
|
||||
},
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class DeploymentHealthView(View):
|
||||
"""Return live sentinel health JSON for one deployment."""
|
||||
|
||||
def get(self, request: HttpRequest, deployment_id: str) -> JsonResponse:
|
||||
"""Return JSON probe state for one deployment sentinel endpoint."""
|
||||
del request
|
||||
deployment = get_object_or_404(_deployment_queryset(), pk=deployment_id)
|
||||
return JsonResponse(probe_test_deployment_health(deployment))
|
||||
|
||||
|
||||
class DeploymentDashboardHomeView(DeploymentDashboardView):
|
||||
"""Alias the dashboard at the site root for local testing convenience."""
|
||||
|
||||
template_name = "control_plane/deployment_dashboard.html"
|
||||
|
||||
|
||||
def _build_deployment_card(deployment: Deployment) -> DeploymentCard:
|
||||
return DeploymentCard(
|
||||
deployment=deployment,
|
||||
sentinel_url=build_test_django_local_url(deployment),
|
||||
runtime_services=tuple(deployment.runtime_services.all()),
|
||||
)
|
||||
|
||||
|
||||
def _build_status_summaries() -> tuple[DeploymentStatusSummary, ...]:
|
||||
summary_rows = tuple(
|
||||
Deployment.objects.values("status").annotate(total=Count("id")).order_by("status"),
|
||||
)
|
||||
return tuple(
|
||||
DeploymentStatusSummary(
|
||||
status=status,
|
||||
label=_resolve_status_label(status),
|
||||
total=int(total),
|
||||
)
|
||||
for status, total in ((row["status"], row["total"]) for row in summary_rows)
|
||||
)
|
||||
|
||||
|
||||
def _resolve_status_label(status: str) -> str:
|
||||
for choice in DeploymentStatus:
|
||||
if choice.value == status:
|
||||
return choice.label
|
||||
|
||||
return status.replace("-", " ").title()
|
||||
Loading…
Add table
Add a link
Reference in a new issue