Initial commit
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
const maxHistory = 100
|
||||
|
||||
// HistoryEntry records one completed HTTP transaction.
|
||||
type HistoryEntry struct {
|
||||
At time.Time
|
||||
Name string
|
||||
Method string
|
||||
URL string
|
||||
StatusCode int
|
||||
Status string
|
||||
Duration time.Duration
|
||||
Size int
|
||||
ContentType string
|
||||
IsError bool
|
||||
ErrMsg string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Formatting helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func fmtDuration(d time.Duration) string {
|
||||
switch {
|
||||
case d < time.Millisecond:
|
||||
return "< 1ms"
|
||||
case d < time.Second:
|
||||
return fmt.Sprintf("%dms", d.Milliseconds())
|
||||
case d < time.Minute:
|
||||
return fmt.Sprintf("%.1fs", d.Seconds())
|
||||
default:
|
||||
m := int(d.Minutes())
|
||||
s := int(d.Seconds()) % 60
|
||||
return fmt.Sprintf("%dm%ds", m, s)
|
||||
}
|
||||
}
|
||||
|
||||
func fmtSize(n int) string {
|
||||
switch {
|
||||
case n < 1024:
|
||||
return fmt.Sprintf("%d B", n)
|
||||
case n < 1024*1024:
|
||||
return fmt.Sprintf("%.1f KB", float64(n)/1024)
|
||||
default:
|
||||
return fmt.Sprintf("%.1f MB", float64(n)/1024/1024)
|
||||
}
|
||||
}
|
||||
|
||||
func durationStyle(d time.Duration) lipgloss.Style {
|
||||
switch {
|
||||
case d < 200*time.Millisecond:
|
||||
return lipgloss.NewStyle().Foreground(colorGreen)
|
||||
case d < time.Second:
|
||||
return lipgloss.NewStyle().Foreground(colorYellow)
|
||||
default:
|
||||
return lipgloss.NewStyle().Foreground(colorRed)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Stats bar (rendered inside the response panel, below the viewport)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// renderStatsBar returns a single line summarising the last response.
|
||||
func renderStatsBar(e HistoryEntry, width int) string {
|
||||
if e.IsError {
|
||||
return statusErrStyle.Render(" ✗ " + e.ErrMsg)
|
||||
}
|
||||
|
||||
status := statusCodeStyle(e.StatusCode).Render(e.Status)
|
||||
dur := durationStyle(e.Duration).Render("⚡ " + fmtDuration(e.Duration))
|
||||
size := lipgloss.NewStyle().Foreground(colorFg).Render("◎ " + fmtSize(e.Size))
|
||||
|
||||
sep := dimItemStyle.Render(" │ ")
|
||||
bar := " " + status + sep + dur + sep + size
|
||||
|
||||
// Append content-type if it fits
|
||||
if e.ContentType != "" {
|
||||
candidate := bar + sep + dimItemStyle.Render(e.ContentType)
|
||||
if lipgloss.Width(candidate) <= width-2 {
|
||||
bar = candidate
|
||||
}
|
||||
}
|
||||
return bar
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// History overlay table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// renderHistoryTable builds the full text content for the history viewport.
|
||||
func renderHistoryTable(history []HistoryEntry, vpWidth int) string {
|
||||
if len(history) == 0 {
|
||||
return hintStyle.Render("\n No requests yet in this session.")
|
||||
}
|
||||
|
||||
// Column widths
|
||||
const (
|
||||
colTime = 8 // HH:MM:SS
|
||||
colMethod = 7 // DELETE + space
|
||||
colStatus = 10 // "200 OK" etc.
|
||||
colDur = 9 // "1.2s" etc.
|
||||
colSize = 8 // "4.2 KB"
|
||||
colSep = 2 // " "
|
||||
)
|
||||
fixedW := colTime + colMethod + colStatus + colDur + colSize + colSep*5
|
||||
nameW := vpWidth - fixedW - 2
|
||||
if nameW < 10 {
|
||||
nameW = 10
|
||||
}
|
||||
|
||||
header := buildHistoryRow(
|
||||
dimItemStyle,
|
||||
"Time", "Method", "Status", "Duration", "Size", "Name",
|
||||
colTime, colMethod, colStatus, colDur, colSize, nameW,
|
||||
)
|
||||
divider := dimItemStyle.Render(strings.Repeat("─", vpWidth-2))
|
||||
|
||||
var sb strings.Builder
|
||||
sb.WriteString(header + "\n")
|
||||
sb.WriteString(divider + "\n")
|
||||
|
||||
// Most recent first
|
||||
for i := len(history) - 1; i >= 0; i-- {
|
||||
e := history[i]
|
||||
|
||||
timeStr := e.At.Format("15:04:05")
|
||||
method := e.Method
|
||||
status := e.Status
|
||||
dur := fmtDuration(e.Duration)
|
||||
size := fmtSize(e.Size)
|
||||
name := e.Name
|
||||
if len(name) > nameW {
|
||||
name = name[:nameW-1] + "…"
|
||||
}
|
||||
|
||||
var rowStyle lipgloss.Style
|
||||
if e.IsError {
|
||||
rowStyle = lipgloss.NewStyle().Foreground(colorRed)
|
||||
} else {
|
||||
rowStyle = lipgloss.NewStyle().Foreground(colorFg)
|
||||
}
|
||||
|
||||
methodStr := methodStyle(method).Render(fmt.Sprintf("%-*s", colMethod, method))
|
||||
statusStr := statusCodeStyle(e.StatusCode).Render(fmt.Sprintf("%-*s", colStatus, status))
|
||||
durStr := durationStyle(e.Duration).Render(fmt.Sprintf("%-*s", colDur, dur))
|
||||
|
||||
row := rowStyle.Render(timeStr+" ") +
|
||||
methodStr + " " +
|
||||
statusStr + " " +
|
||||
durStr + " " +
|
||||
rowStyle.Render(fmt.Sprintf("%-*s", colSize, size)+" "+name)
|
||||
|
||||
sb.WriteString(row + "\n")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func buildHistoryRow(s lipgloss.Style, t, method, status, dur, size, name string,
|
||||
wT, wM, wSt, wD, wSz, wN int) string {
|
||||
return s.Render(
|
||||
fmt.Sprintf("%-*s %-*s %-*s %-*s %-*s %-*s",
|
||||
wT, t,
|
||||
wM, method,
|
||||
wSt, status,
|
||||
wD, dur,
|
||||
wSz, size,
|
||||
wN, name,
|
||||
),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user