diff --git a/internal/proxy/handler.go b/internal/proxy/handler.go index 3ec26f3..2100eeb 100644 --- a/internal/proxy/handler.go +++ b/internal/proxy/handler.go @@ -65,7 +65,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeWarn, - Body: fmt.Sprintf("(%s) failed to read request body", request.ID), + Body: fmt.Sprintf("(%s) failed to read request body: %v", getEndOfUUID(request.ID), err), Request: request, } } else { @@ -86,7 +86,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeRequestStarted, - Body: fmt.Sprintf("-> %+v", request), + Body: fmt.Sprintf("(%s) %s %s", getEndOfUUID(request.ID), request.Method, request.RawURL), Request: request, } @@ -103,7 +103,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeRequestFailed, - Body: fmt.Sprintf("upstream error for %s %s: %v", outReq.Method, outReq.URL.String(), err), + Body: fmt.Sprintf("(%s) upstream error: %v", getEndOfUUID(request.ID), err), Request: request, } return @@ -115,7 +115,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeWarn, - Body: fmt.Sprintf("(%s) failed to read response body", request.ID), + Body: fmt.Sprintf("(%s) failed to read response body: %v", getEndOfUUID(request.ID), err), Request: request, } } else { @@ -133,7 +133,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeRequestFailed, - Body: fmt.Sprintf("write response body %s %s: %v", outReq.Method, outReq.URL.String(), err), + Body: fmt.Sprintf("(%s) failed to write response body: %v", getEndOfUUID(request.ID), err), } return } @@ -146,7 +146,7 @@ func proxyHandler(ch chan<- model.Event) http.Handler { ch <- model.Event{ Time: time.Now().Local(), Type: model.EventTypeRequestFinished, - Body: fmt.Sprintf("<- %+v %s", request, formatHeaders(resp.Request.Header)), + Body: fmt.Sprintf("(%s) %s %s %d %dms", getEndOfUUID(request.ID), request.Method, request.RawURL, request.Status, request.Duration.Milliseconds()), Request: request, } }) @@ -199,6 +199,10 @@ func formatHeaders(headers http.Header) string { return strings.Join(parts, ", ") } +func getEndOfUUID(id uuid.UUID) string { + return id.String()[24:] +} + // BUG: Not sure if this actually works, seems to favor the 502 func statusFromUpstreamError(req *http.Request, resp *http.Response, err error) int { if resp != nil { diff --git a/internal/tui/panes.go b/internal/tui/panes.go index ae2b51f..5c9d88c 100644 --- a/internal/tui/panes.go +++ b/internal/tui/panes.go @@ -23,7 +23,7 @@ func (m Model) renderStatusBar(w int) string { avg := int(msSum) / max(1, len(m.requests)) left := fmt.Sprintf(" tap %3d reqs | %d err | avg %dms", len(m.requests), errCount, avg) - right := "j/k nav / search tab panel e events o output r replay q quit " + right := "j/k nav / search tab panel e events o output r replay ctrl+r restart q quit " spaceSize := max(w-(len(left)+len(right)), 0) space := strings.Repeat(" ", spaceSize) @@ -31,6 +31,7 @@ func (m Model) renderStatusBar(w int) string { return m.theme.Header.Render(left + space + right) } +// TODO: Implement func (m Model) renderSearchPane(w, h int) []string { lines := make([]string, h) for y := range lines { @@ -91,6 +92,7 @@ func (m Model) renderRequestPane(w, h int) []string { return lines } +// TODO: Implement func (m Model) renderDetailsPane(w, h int) []string { lines := make([]string, h) for y := range lines { @@ -123,22 +125,33 @@ func (m Model) renderEventsPane(w, h int) []string { lines := []string{status} for _, event := range events { - line := fmt.Sprintf( - "%s %-15s %s", - event.Time.Format("15:04:05"), - event.Type, - event.Body, + var ( + eTime string = m.theme.TextMuted.Render(event.Time.Format("15:04:05") + " ") + eType string = getEventColor(m.theme, event.Type).Render(fmt.Sprintf("%-15s ", event.Type)) + + avail int = max(0, w-lipgloss.Width(eTime+eType)) + body string = clampRendered(m.theme.Text.Render(event.Body), avail) ) - if event.PID > 0 { - line = fmt.Sprintf( - "%s %-15s %d %s", - event.Time.Format("15:04:05"), - event.Type, - event.PID, - event.Body, - ) + + if event.Type == model.EventTypeRequestFailed || event.Type == model.EventTypeFatal { + body = clampRendered(m.theme.TextError.Render(event.Body), avail) + eTime = m.theme.TextMutedError.Render(event.Time.Format("15:04:05") + " ") } - lines = append(lines, truncate(line, w)) + + line := eTime + eType + body + if event.PID > 0 { + pid := m.theme.TextMuted.Render(fmt.Sprintf("%d ", event.PID)) + + avail = max(0, w-lipgloss.Width(eTime+eType+pid)) + body = clampRendered(m.theme.Text.Render(event.Body), avail) + line = eTime + eType + pid + body + } + + if event.Type == model.EventTypeRequestFailed || event.Type == model.EventTypeFatal { + line += m.theme.TextError.Render(strings.Repeat(" ", w-lipgloss.Width(line))) + } + + lines = append(lines, line) } // Cleanup diff --git a/internal/tui/style.go b/internal/tui/style.go index 894c469..c31534b 100644 --- a/internal/tui/style.go +++ b/internal/tui/style.go @@ -3,6 +3,7 @@ package tui import ( "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/x/ansi" + "termtap.dev/internal/model" ) type Theme struct { @@ -17,10 +18,15 @@ type Theme struct { TextError lipgloss.Style TextMutedError lipgloss.Style - EventGreen lipgloss.Style - EventRed lipgloss.Style - EventBlue lipgloss.Style - EventOrange lipgloss.Style + EventDefault lipgloss.Style + EventSession lipgloss.Style + EventProcess lipgloss.Style + EventProxy lipgloss.Style + EventRequestInFlight lipgloss.Style + EventSuccess lipgloss.Style + EventWarn lipgloss.Style + EventError lipgloss.Style + EventFatal lipgloss.Style } const background = lipgloss.Color("#010e1f") @@ -29,8 +35,11 @@ const text = lipgloss.Color("#dfe5ed") const textMuted = lipgloss.Color("#7c7e80") const blue = lipgloss.Color("#2280f2") +const cyan = lipgloss.Color("#22b8f2") +const violetBlue = lipgloss.Color("#6f7dff") const orange = lipgloss.Color("#f2a813") const red = lipgloss.Color("#e6130b") +const fatalRed = lipgloss.Color("#ff4d4d") const green = lipgloss.Color("#10e31e") func newTheme() Theme { @@ -64,18 +73,42 @@ func newTheme() Theme { Foreground(textMuted). Background(backgroundError), - EventGreen: lipgloss.NewStyle(). - Foreground(green). - Background(background), - EventBlue: lipgloss.NewStyle(). + EventDefault: lipgloss.NewStyle(). + Foreground(text). + Background(background). + Bold(true), + EventSession: lipgloss.NewStyle(). + Foreground(textMuted). + Background(background). + Bold(true), + EventProcess: lipgloss.NewStyle(). Foreground(blue). - Background(background), - EventRed: lipgloss.NewStyle(). - Foreground(red). - Background(backgroundError), - EventOrange: lipgloss.NewStyle(). + Background(background). + Bold(true), + EventProxy: lipgloss.NewStyle(). + Foreground(violetBlue). + Background(background). + Bold(true), + EventRequestInFlight: lipgloss.NewStyle(). + Foreground(cyan). + Background(background). + Bold(true), + EventSuccess: lipgloss.NewStyle(). + Foreground(green). + Background(background). + Bold(true), + EventWarn: lipgloss.NewStyle(). Foreground(orange). - Background(background), + Background(background). + Bold(true), + EventError: lipgloss.NewStyle(). + Foreground(red). + Background(backgroundError). + Bold(true), + EventFatal: lipgloss.NewStyle(). + Foreground(fatalRed). + Background(backgroundError). + Bold(true), } } @@ -88,3 +121,42 @@ func clampRendered(s string, maxCols int) string { } return ansi.Truncate(s, maxCols, "...") } + +func getEventColor(theme Theme, event model.EventType) lipgloss.Style { + switch event { + case model.EventTypeSessionStarted, + model.EventTypeSessionStopped: + return theme.EventSession + + case model.EventTypeProxyStarted, + model.EventTypeProxyStarting, + model.EventTypeProxyStopped: + return theme.EventProxy + + case model.EventTypeRequestStarted: + return theme.EventRequestInFlight + + case model.EventTypeRequestFinished: + return theme.EventSuccess + + case model.EventTypeFatal: + return theme.EventFatal + + case model.EventTypeRequestFailed: + return theme.EventError + + case model.EventTypeProcessStarting, + model.EventTypeProcessStarted, + model.EventTypeProcessExited, + model.EventTypeProcessSignaled, + model.EventTypeProcessStdout, + model.EventTypeProcessStderr: + return theme.EventProcess + + case model.EventTypeWarn: + return theme.EventWarn + + default: + return theme.EventDefault + } +}