package handler import ( "context" "encoding/json" "fmt" "net/http" "sort" "strconv" "time" "github.com/certctl-io/certctl/internal/api/middleware" "github.com/certctl-io/certctl/internal/service" ) // MetricsService defines the service interface for metrics collection. type MetricsService interface { GetDashboardSummary(ctx context.Context) (interface{}, error) } // CounterSnapshotter is the minimum surface MetricsHandler consumes // from a counter table for the Prometheus exposer. The OCSPCounters // type in internal/service satisfies this; future per-area counter // tabs (CRL, cert-export, EST, SCEP, Intune) plug in the same way. // // Production hardening II Phase 8. type CounterSnapshotter interface { Snapshot() map[string]uint64 } // DeploySnapshotEntry is the per-target-type tuple emitted by the // deploy package's counter table. Avoids importing the service // package's DeploySnapshot directly so the handler stays // dependency-light (the interface uses primitives only). // // Phase 10 of the deploy-hardening I master bundle. type DeploySnapshotEntry struct { TargetType string AttemptsSuccess uint64 AttemptsFailure uint64 ValidateFailures uint64 ReloadFailures uint64 PostVerifyFails uint64 RollbackRestored uint64 RollbackAlsoFail uint64 IdempotentSkips uint64 } // DeployCounterSnapshotter is the surface MetricsHandler consumes // for the per-target-type deploy counters. The DeployCounters type // in internal/service satisfies this via an adapter. type DeployCounterSnapshotter interface { Snapshot() []DeploySnapshotEntry } // IssuanceCounterEntry / IssuanceFailureEntry / IssuanceDurationEntry // and the IssuanceMetricsSnapshotter interface live in // internal/service (issuance_metrics.go). Handler can't define them // locally because internal/api/handler is imported by service — the // reverse import would create a cycle. The exposer below takes the // types via the interface defined in service. // VaultRenewalSnapshotter is the surface MetricsHandler consumes // to emit the certctl_vault_token_renewals_total{result=...} // counter. *service.VaultRenewalMetrics satisfies this; cmd/server // passes the same instance into IssuerRegistry.SetVaultRenewalMetrics // (so Vault connectors record results) AND into // MetricsHandler.SetVaultRenewals (so the Prometheus exposer reads // the counters). // // Returns three counter values directly (rather than a shared struct // type) so service can satisfy this without an import cycle — // handler already imports service for IssuanceMetricsSnapshotter, // but service does not import handler. A method that returns // (uint64, uint64, uint64) needs no shared type. // // Top-10 fix #5 of the 2026-05-03 issuer-coverage audit. type VaultRenewalSnapshotter interface { // SnapshotVaultRenewals returns success, failure, and // not_renewable counters as point-in-time reads. Order is fixed // for the exposer — matches the Prometheus label order. SnapshotVaultRenewals() (success, failure, notRenewable uint64) } // ExpiryAlertSnapshotter is the surface MetricsHandler consumes to // emit certctl_expiry_alerts_total{channel, threshold, result}. // *service.ExpiryAlertMetrics satisfies this. Same wiring shape as // VaultRenewalSnapshotter — one instance shared between recording // (via NotificationService.SetExpiryAlertMetrics) and exposing // (here). // // Rank 4 of the 2026-05-03 Infisical deep-research deliverable // (cowork/infisical-deep-research-results.md Part 5). type ExpiryAlertSnapshotter interface { // SnapshotExpiryAlerts returns one entry per non-zero counter, // pre-sorted by (channel, threshold, result) so the Prometheus // exposition is byte-stable across requests. The handler does // not re-sort. SnapshotExpiryAlerts() []service.ExpiryAlertSnapshotEntry } // MetricsHandler handles HTTP requests for metrics. // Supports both JSON format (GET /api/v1/metrics) and Prometheus exposition format // (GET /api/v1/metrics/prometheus) for integration with Prometheus, Grafana, Datadog, etc. type MetricsHandler struct { svc MetricsService serverStarted time.Time // Production hardening II Phase 8 — per-area counter snapshotters. // nil values omit the corresponding metric block; cmd/server/main.go // wires the instances at startup. The naming convention is // certctl__