package handler import ( "encoding/json" "fmt" "html/template" "net/http" "time" "github.com/elfoundation/hatch/internal/store" ) // inspectPageData is passed to the inspect template. type inspectPageData struct { EndpointID string Requests []*store.Request } // inspectTemplate is the server-rendered HTML for the inspect page. var inspectTemplate = template.Must(template.New("inspect").Funcs(template.FuncMap{ "prettyJSON": prettyJSON, "formatTime": formatTime, }).Parse(` Hatch — {{.EndpointID}}

Hatch

Endpoint: {{.EndpointID}}
{{if .Requests}} {{range .Requests}}
{{.Method}} {{.Path}} {{formatTime .CreatedAt}}
{{if .Headers}}
{{prettyJSON .Headers}}
{{end}} {{if .Query}}
{{.Query}}
{{end}} {{if .Body}}
{{printf "%s" .Body}}
{{end}}
{{end}} {{else}}

Waiting for requests...

Send a request to /{{.EndpointID}} to see it appear here.

{{end}} `)) // HandleInspect serves the inspect page at GET /e/{endpointID}. func HandleInspect(repo store.Repository) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { endpointID := r.PathValue("endpointID") if endpointID == "" { http.Error(w, "missing endpoint ID", http.StatusBadRequest) return } if _, err := repo.GetEndpoint(r.Context(), endpointID); err != nil { repo.CreateEndpoint(r.Context(), endpointID) } requests, err := repo.ListRequests(r.Context(), endpointID, 100) if err != nil { http.Error(w, "failed to list requests", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") inspectTemplate.Execute(w, inspectPageData{ EndpointID: endpointID, Requests: requests, }) } } // prettyJSON formats a JSON string with indentation. func prettyJSON(raw string) string { var v interface{} if err := json.Unmarshal([]byte(raw), &v); err != nil { return raw } b, err := json.MarshalIndent(v, "", " ") if err != nil { return raw } return string(b) } // formatTime displays an ISO 8601 timestamp as a short relative or absolute string. func formatTime(raw string) string { t, err := time.Parse("2006-01-02T15:04:05.000Z07:00", raw) if err != nil { // Try a few common variants. t, err = time.Parse("2006-01-02T15:04:05Z07:00", raw) } if err != nil { return raw } d := time.Since(t) switch { case d < 5*time.Second: return "just now" case d < time.Minute: return fmt.Sprintf("%ds ago", int(d.Seconds())) case d < time.Hour: return fmt.Sprintf("%dm ago", int(d.Minutes())) case d < 24*time.Hour: return fmt.Sprintf("%dh ago", int(d.Hours())) case d < 7*24*time.Hour: return fmt.Sprintf("%dd ago", int(d.Hours()/24)) default: return t.Format("Jan 2, 15:04") } }