feat: enhance /api/v1/status endpoint with full monitoring data #86
@@ -2,22 +2,217 @@ package handlers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"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.
|
// HandleStatus returns the monitoring status handler.
|
||||||
func (h *Handlers) HandleStatus() http.HandlerFunc {
|
func (h *Handlers) HandleStatus() http.HandlerFunc {
|
||||||
type response struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(
|
return func(
|
||||||
writer http.ResponseWriter,
|
writer http.ResponseWriter,
|
||||||
request *http.Request,
|
request *http.Request,
|
||||||
) {
|
) {
|
||||||
|
snap := h.state.GetSnapshot()
|
||||||
|
|
||||||
|
resp := buildStatusResponse(snap)
|
||||||
|
|
||||||
h.respondJSON(
|
h.respondJSON(
|
||||||
writer, request,
|
writer, request,
|
||||||
&response{Status: "ok"},
|
resp,
|
||||||
http.StatusOK,
|
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