feat: enhance /api/v1/status endpoint with full monitoring data #86
@@ -2,22 +2,217 @@ package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"sneak.berlin/go/dnswatcher/internal/state"
|
||||
)
|
||||
|
||||
// statusDomainInfo holds status information for a monitored domain.
|
||||
type statusDomainInfo struct {
|
||||
Nameservers []string `json:"nameservers"`
|
||||
LastChecked time.Time `json:"lastChecked"`
|
||||
}
|
||||
|
||||
// statusHostnameNSInfo holds per-nameserver status for a hostname.
|
||||
type statusHostnameNSInfo struct {
|
||||
Records map[string][]string `json:"records"`
|
||||
Status string `json:"status"`
|
||||
LastChecked time.Time `json:"lastChecked"`
|
||||
}
|
||||
|
||||
// statusHostnameInfo holds status information for a monitored hostname.
|
||||
type statusHostnameInfo struct {
|
||||
Nameservers map[string]*statusHostnameNSInfo `json:"nameservers"`
|
||||
LastChecked time.Time `json:"lastChecked"`
|
||||
}
|
||||
|
||||
// statusPortInfo holds status information for a monitored port.
|
||||
type statusPortInfo struct {
|
||||
Open bool `json:"open"`
|
||||
Hostnames []string `json:"hostnames"`
|
||||
LastChecked time.Time `json:"lastChecked"`
|
||||
}
|
||||
|
||||
// statusCertificateInfo holds status information for a TLS certificate.
|
||||
type statusCertificateInfo struct {
|
||||
CommonName string `json:"commonName"`
|
||||
Issuer string `json:"issuer"`
|
||||
NotAfter time.Time `json:"notAfter"`
|
||||
SubjectAlternativeNames []string `json:"subjectAlternativeNames"`
|
||||
Status string `json:"status"`
|
||||
LastChecked time.Time `json:"lastChecked"`
|
||||
}
|
||||
|
||||
// statusCounts holds summary counts of monitored resources.
|
||||
type statusCounts struct {
|
||||
Domains int `json:"domains"`
|
||||
Hostnames int `json:"hostnames"`
|
||||
Ports int `json:"ports"`
|
||||
PortsOpen int `json:"portsOpen"`
|
||||
Certificates int `json:"certificates"`
|
||||
CertsOK int `json:"certificatesOk"`
|
||||
CertsError int `json:"certificatesError"`
|
||||
}
|
||||
|
||||
// statusResponse is the full /api/v1/status response.
|
||||
type statusResponse struct {
|
||||
Status string `json:"status"`
|
||||
LastUpdated time.Time `json:"lastUpdated"`
|
||||
Counts statusCounts `json:"counts"`
|
||||
Domains map[string]*statusDomainInfo `json:"domains"`
|
||||
Hostnames map[string]*statusHostnameInfo `json:"hostnames"`
|
||||
Ports map[string]*statusPortInfo `json:"ports"`
|
||||
Certificates map[string]*statusCertificateInfo `json:"certificates"`
|
||||
}
|
||||
|
||||
// HandleStatus returns the monitoring status handler.
|
||||
func (h *Handlers) HandleStatus() http.HandlerFunc {
|
||||
type response struct {
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
return func(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
) {
|
||||
snap := h.state.GetSnapshot()
|
||||
|
||||
resp := buildStatusResponse(snap)
|
||||
|
||||
h.respondJSON(
|
||||
writer, request,
|
||||
&response{Status: "ok"},
|
||||
resp,
|
||||
http.StatusOK,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// buildStatusResponse constructs the full status response from
|
||||
// the current monitoring snapshot.
|
||||
func buildStatusResponse(
|
||||
snap state.Snapshot,
|
||||
) *statusResponse {
|
||||
resp := &statusResponse{
|
||||
Status: "ok",
|
||||
LastUpdated: snap.LastUpdated,
|
||||
Domains: make(map[string]*statusDomainInfo),
|
||||
Hostnames: make(map[string]*statusHostnameInfo),
|
||||
Ports: make(map[string]*statusPortInfo),
|
||||
Certificates: make(map[string]*statusCertificateInfo),
|
||||
}
|
||||
|
||||
buildDomains(snap, resp)
|
||||
buildHostnames(snap, resp)
|
||||
buildPorts(snap, resp)
|
||||
buildCertificates(snap, resp)
|
||||
buildCounts(resp)
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func buildDomains(
|
||||
snap state.Snapshot,
|
||||
resp *statusResponse,
|
||||
) {
|
||||
for name, ds := range snap.Domains {
|
||||
ns := make([]string, len(ds.Nameservers))
|
||||
copy(ns, ds.Nameservers)
|
||||
sort.Strings(ns)
|
||||
|
||||
resp.Domains[name] = &statusDomainInfo{
|
||||
Nameservers: ns,
|
||||
LastChecked: ds.LastChecked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildHostnames(
|
||||
snap state.Snapshot,
|
||||
resp *statusResponse,
|
||||
) {
|
||||
for name, hs := range snap.Hostnames {
|
||||
info := &statusHostnameInfo{
|
||||
Nameservers: make(map[string]*statusHostnameNSInfo),
|
||||
LastChecked: hs.LastChecked,
|
||||
}
|
||||
|
||||
for ns, nsState := range hs.RecordsByNameserver {
|
||||
recs := make(map[string][]string, len(nsState.Records))
|
||||
for rtype, vals := range nsState.Records {
|
||||
copied := make([]string, len(vals))
|
||||
copy(copied, vals)
|
||||
recs[rtype] = copied
|
||||
}
|
||||
|
||||
info.Nameservers[ns] = &statusHostnameNSInfo{
|
||||
Records: recs,
|
||||
Status: nsState.Status,
|
||||
LastChecked: nsState.LastChecked,
|
||||
}
|
||||
}
|
||||
|
||||
resp.Hostnames[name] = info
|
||||
}
|
||||
}
|
||||
|
||||
func buildPorts(
|
||||
snap state.Snapshot,
|
||||
resp *statusResponse,
|
||||
) {
|
||||
for key, ps := range snap.Ports {
|
||||
hostnames := make([]string, len(ps.Hostnames))
|
||||
copy(hostnames, ps.Hostnames)
|
||||
sort.Strings(hostnames)
|
||||
|
||||
resp.Ports[key] = &statusPortInfo{
|
||||
Open: ps.Open,
|
||||
Hostnames: hostnames,
|
||||
LastChecked: ps.LastChecked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildCertificates(
|
||||
snap state.Snapshot,
|
||||
resp *statusResponse,
|
||||
) {
|
||||
for key, cs := range snap.Certificates {
|
||||
sans := make([]string, len(cs.SubjectAlternativeNames))
|
||||
copy(sans, cs.SubjectAlternativeNames)
|
||||
|
||||
resp.Certificates[key] = &statusCertificateInfo{
|
||||
CommonName: cs.CommonName,
|
||||
Issuer: cs.Issuer,
|
||||
NotAfter: cs.NotAfter,
|
||||
SubjectAlternativeNames: sans,
|
||||
Status: cs.Status,
|
||||
LastChecked: cs.LastChecked,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildCounts(resp *statusResponse) {
|
||||
var portsOpen, certsOK, certsError int
|
||||
|
||||
for _, ps := range resp.Ports {
|
||||
if ps.Open {
|
||||
portsOpen++
|
||||
}
|
||||
}
|
||||
|
||||
for _, cs := range resp.Certificates {
|
||||
switch cs.Status {
|
||||
case "ok":
|
||||
certsOK++
|
||||
case "error":
|
||||
certsError++
|
||||
}
|
||||
}
|
||||
|
||||
resp.Counts = statusCounts{
|
||||
Domains: len(resp.Domains),
|
||||
Hostnames: len(resp.Hostnames),
|
||||
Ports: len(resp.Ports),
|
||||
PortsOpen: portsOpen,
|
||||
Certificates: len(resp.Certificates),
|
||||
CertsOK: certsOK,
|
||||
CertsError: certsError,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user