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}}
{{if .Headers}}
Headers
{{prettyJSON .Headers}}
{{end}}
{{if .Query}}
{{end}}
{{if .Body}}
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")
}
}