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 { return func( writer http.ResponseWriter, request *http.Request, ) { snap := h.state.GetSnapshot() resp := buildStatusResponse(snap) h.respondJSON( writer, request, 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, } }