Compare commits
No commits in common. "d26d812d3c49b12a4939948f63887745af27021e" and "4c4a3f6bb065f2bd062b66963e5a45922558c17a" have entirely different histories.
d26d812d3c
...
4c4a3f6bb0
@ -13,7 +13,6 @@ import (
|
||||
|
||||
func StartProcess(cmd model.Command, addr string, ch chan<- model.Event) (*model.Process, error) {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProcessStarting,
|
||||
Body: fmt.Sprintf("spawning process '%s'", process.CommandString(cmd)),
|
||||
}
|
||||
@ -36,7 +35,6 @@ func StopProcess(proc *model.Process, ch chan<- model.Event, sig syscall.Signal)
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProcessSignaled,
|
||||
Body: fmt.Sprintf("process with pid '%d' is being killed", proc.Exec.Process.Pid),
|
||||
PID: proc.Exec.Process.Pid,
|
||||
@ -60,7 +58,6 @@ func waitForProcessExit(proc *model.Process, ch chan<- model.Event) {
|
||||
if err := proc.Exec.Wait(); err != nil {
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProcessExited,
|
||||
Body: fmt.Sprintf("process pid '%d' exited", proc.Exec.Process.Pid),
|
||||
PID: proc.Exec.Process.Pid,
|
||||
@ -71,7 +68,6 @@ func waitForProcessExit(proc *model.Process, ch chan<- model.Event) {
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeFatal,
|
||||
Body: fmt.Sprintf("%q", err),
|
||||
}
|
||||
@ -80,7 +76,6 @@ func waitForProcessExit(proc *model.Process, ch chan<- model.Event) {
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProcessExited,
|
||||
Body: fmt.Sprintf("process pid '%d' exited", proc.Exec.Process.Pid),
|
||||
PID: proc.Exec.Process.Pid,
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
@ -15,7 +14,6 @@ func StartProxy(ps *model.ProxyServer, ch chan<- model.Event) {
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProxyStarting,
|
||||
Body: fmt.Sprintf("proxy server started on %s", (*ps.Listener).Addr().String()),
|
||||
}
|
||||
@ -26,7 +24,6 @@ func StartProxy(ps *model.ProxyServer, ch chan<- model.Event) {
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeFatal,
|
||||
Body: fmt.Sprintf("fatal error in proxy server: %q", err),
|
||||
}
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type EventType string
|
||||
|
||||
const (
|
||||
@ -28,7 +26,6 @@ const (
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
Time time.Time
|
||||
Type EventType
|
||||
Body string
|
||||
PID int
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
@ -25,7 +24,6 @@ func NewProcess(cmd model.Command, addr string, ch chan<- model.Event) *model.Pr
|
||||
stdout, err := proc.StdoutPipe()
|
||||
if err != nil {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("could not open stdout pipe: %q", err),
|
||||
PID: proc.Process.Pid,
|
||||
@ -37,7 +35,6 @@ func NewProcess(cmd model.Command, addr string, ch chan<- model.Event) *model.Pr
|
||||
stderr, err := proc.StderrPipe()
|
||||
if err != nil {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("could not open stderr pipe: %q", err),
|
||||
PID: proc.Process.Pid,
|
||||
@ -73,7 +70,6 @@ func readPipe(pipe io.Reader, t model.EventType, ch chan<- model.Event) {
|
||||
scanner := bufio.NewScanner(pipe)
|
||||
for scanner.Scan() {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: t,
|
||||
Body: scanner.Text(),
|
||||
}
|
||||
@ -104,7 +100,6 @@ func UpdateStatus(proc *model.Process, running bool, ch chan<- model.Event) {
|
||||
}
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: t,
|
||||
Body: fmt.Sprintf("Set process pid '%d' status to %s", proc.Exec.Process.Pid, status),
|
||||
PID: proc.Exec.Process.Pid,
|
||||
|
||||
@ -28,7 +28,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
if req.Method == http.MethodConnect {
|
||||
http.Error(w, "CONNECT is not supported yet", http.StatusNotImplemented)
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("CONNECT is not supported: %s", req.Host),
|
||||
}
|
||||
@ -38,7 +37,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
if req.URL.Scheme == "" || req.URL.Host == "" {
|
||||
http.Error(w, "request must use absolute-form URLs through the proxy", http.StatusBadRequest)
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("rejected non-proxy request %s %s", req.Method, req.URL.String()),
|
||||
}
|
||||
@ -63,7 +61,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
requestPreview, err := readAndRestoreBody(&req.Body)
|
||||
if err != nil {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("(%s) failed to read request body", request.ID),
|
||||
Request: request,
|
||||
@ -84,7 +81,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
request.RawURL = outReq.URL.String()
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeRequestStarted,
|
||||
Body: fmt.Sprintf("-> %+v", request),
|
||||
Request: request,
|
||||
@ -101,7 +97,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
request.Status = status
|
||||
|
||||
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),
|
||||
Request: request,
|
||||
@ -113,7 +108,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
responsePreview, err := readAndRestoreBody(&resp.Body)
|
||||
if err != nil {
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("(%s) failed to read response body", request.ID),
|
||||
Request: request,
|
||||
@ -131,7 +125,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
request.Status = resp.StatusCode
|
||||
|
||||
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),
|
||||
}
|
||||
@ -144,7 +137,6 @@ func proxyHandler(ch chan<- model.Event) http.Handler {
|
||||
request.Pending = false
|
||||
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeRequestFinished,
|
||||
Body: fmt.Sprintf("<- %+v %s", request, formatHeaders(resp.Request.Header)),
|
||||
Request: request,
|
||||
|
||||
@ -35,7 +35,6 @@ func Destroy(ps *model.ProxyServer, ch chan<- model.Event) {
|
||||
if ps != nil && ps.Server != nil {
|
||||
_ = ps.Server.Shutdown(ctx)
|
||||
ch <- model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeProxyStarted,
|
||||
Body: "proxy server was destroyed",
|
||||
}
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
import "termtap.dev/internal/model"
|
||||
|
||||
type EventMsg struct {
|
||||
value model.Event
|
||||
@ -14,15 +9,3 @@ type EventMsg struct {
|
||||
type ErrMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type TickMsg struct {
|
||||
Now time.Time
|
||||
}
|
||||
|
||||
const tick = 20 * time.Millisecond
|
||||
|
||||
func tickCmd() tea.Cmd {
|
||||
return tea.Tick(tick, func(t time.Time) tea.Msg {
|
||||
return TickMsg{Now: t}
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"termtap.dev/internal/model"
|
||||
@ -26,8 +25,6 @@ type Model struct {
|
||||
showEvents bool
|
||||
showStd bool
|
||||
showSearch bool
|
||||
|
||||
now time.Time
|
||||
}
|
||||
|
||||
func NewModel(ch <-chan model.Event) Model {
|
||||
@ -50,7 +47,7 @@ func Run(ch <-chan model.Event) error {
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return tea.Batch(waitForEvent(m.channel), tickCmd())
|
||||
return waitForEvent(m.channel)
|
||||
}
|
||||
|
||||
func waitForEvent(ch <-chan model.Event) tea.Cmd {
|
||||
|
||||
@ -1,195 +0,0 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
func (m Model) renderStatusBar(w int) string {
|
||||
var errCount int
|
||||
for _, req := range m.requests {
|
||||
if req.Failed || (req.Status >= 400 && req.Status < 600) {
|
||||
errCount++
|
||||
}
|
||||
}
|
||||
|
||||
left := fmt.Sprintf(" tap %3d reqs | %d err | avg 500ms", len(m.requests), errCount)
|
||||
right := "j/k nav / search tab panel e events o output r replay q quit "
|
||||
|
||||
spaceSize := max(w-(len(left)+len(right)), 0)
|
||||
space := strings.Repeat(" ", spaceSize)
|
||||
|
||||
return left + space + right
|
||||
}
|
||||
|
||||
func (m Model) renderSearchPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat(" ", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderRequestPane(w, h int) []string {
|
||||
var lines []string
|
||||
|
||||
// Render header
|
||||
headerLeft := fmt.Sprintf(" %-7s %-24s %s", "METHOD", "HOST", "PATH")
|
||||
headerRight := fmt.Sprintf("%4s %8s ", "CODE", "TIME")
|
||||
headerSpace := strings.Repeat(" ", max(0, w-len(headerLeft+headerRight)))
|
||||
header := headerLeft + headerSpace + headerRight
|
||||
lines = append(lines, header)
|
||||
|
||||
for i := len(m.requests) - 1; i >= 0; i-- {
|
||||
req := m.requests[i]
|
||||
|
||||
// Some formatting magic here maybe
|
||||
left := fmt.Sprintf(
|
||||
" %-7s %-24s %s",
|
||||
strings.ToUpper(req.Method),
|
||||
req.Host,
|
||||
req.URL,
|
||||
)
|
||||
right := fmt.Sprintf(
|
||||
"%4d %8s ",
|
||||
req.Status,
|
||||
formatDuration(req.Duration),
|
||||
)
|
||||
if req.Pending && !req.StartTime.IsZero() {
|
||||
right = fmt.Sprintf(
|
||||
"%4s %8s ",
|
||||
"",
|
||||
formatDuration(time.Since(req.StartTime)),
|
||||
)
|
||||
}
|
||||
space := strings.Repeat(" ", max(0, w-len(left+right)))
|
||||
|
||||
line := left + space + right
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if len(lines) < h {
|
||||
for i := len(lines); i < h; i++ {
|
||||
lines = append(lines, strings.Repeat(" ", w))
|
||||
}
|
||||
}
|
||||
|
||||
if len(lines) > h {
|
||||
lines = lines[:h]
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderDetailsPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat("^", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
// TODO: This can be done better
|
||||
// TODO: Should h be max or defined?
|
||||
func (m Model) renderEventsPane(w, h int) []string {
|
||||
// Remove the stdout or stderr logs
|
||||
var events []model.Event
|
||||
for _, ev := range m.events {
|
||||
if ev.Type != model.EventTypeProcessStderr &&
|
||||
ev.Type != model.EventTypeProcessStdout {
|
||||
events = append(events, ev)
|
||||
}
|
||||
}
|
||||
|
||||
displayCount := max(h-1, 0)
|
||||
|
||||
if displayCount < len(events) {
|
||||
events = events[len(events)-displayCount:]
|
||||
}
|
||||
|
||||
left := fmt.Sprintf("EVENT LOG - %d EVENTS", len(events))
|
||||
right := "E: TOGGLE"
|
||||
status := left + strings.Repeat(" ", w-len(left+right)) + right
|
||||
lines := []string{status}
|
||||
|
||||
for _, event := range events {
|
||||
line := fmt.Sprintf(
|
||||
"%s %-15s %s",
|
||||
event.Time.Format("15:04:05"),
|
||||
event.Type,
|
||||
event.Body,
|
||||
)
|
||||
if event.PID > 0 {
|
||||
line = fmt.Sprintf(
|
||||
"%s %-15s %d %s",
|
||||
event.Time.Format("15:04:05"),
|
||||
event.Type,
|
||||
event.PID,
|
||||
event.Body,
|
||||
)
|
||||
}
|
||||
lines = append(lines, truncate(line, w))
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if len(lines) < h {
|
||||
for i := len(lines); i < h; i++ {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
// TODO: Should h be max or defined?
|
||||
func (m Model) renderStdPane(w, h int) []string {
|
||||
// Only the stdout or stderr logs
|
||||
var logs []model.Event
|
||||
for _, ev := range m.events {
|
||||
if ev.Type == model.EventTypeProcessStderr ||
|
||||
ev.Type == model.EventTypeProcessStdout {
|
||||
logs = append(logs, ev)
|
||||
}
|
||||
}
|
||||
|
||||
displayCount := max(h-1, 0)
|
||||
|
||||
if displayCount < len(logs) {
|
||||
logs = logs[len(logs)-displayCount:]
|
||||
}
|
||||
|
||||
left := fmt.Sprintf("STDOUT/STDERR LOG - %d LINES", len(logs))
|
||||
right := "O: TOGGLE"
|
||||
status := left + strings.Repeat(" ", w-len(left+right)) + right
|
||||
lines := []string{status}
|
||||
|
||||
for _, log := range logs {
|
||||
var t string
|
||||
if log.Type == model.EventTypeProcessStderr {
|
||||
t = "STDERR"
|
||||
}
|
||||
if log.Type == model.EventTypeProcessStdout {
|
||||
t = "STDOUT"
|
||||
}
|
||||
line := fmt.Sprintf(
|
||||
"%s %6s %s",
|
||||
log.Time.Format("15:04:05"),
|
||||
t,
|
||||
log.Body,
|
||||
)
|
||||
lines = append(lines, truncate(line, w))
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
if len(lines) < h {
|
||||
for i := len(lines); i < h; i++ {
|
||||
lines = append(lines, "")
|
||||
}
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
@ -3,39 +3,36 @@ package tui
|
||||
import "strings"
|
||||
|
||||
func (m Model) renderAppPane() string {
|
||||
// Constant height offset
|
||||
constHeightOffset := 1
|
||||
|
||||
var (
|
||||
searchW int = max(0, m.width)
|
||||
searchW int = m.width
|
||||
searchH int = 1
|
||||
|
||||
reqW int = max(0, int(float64(m.width)*0.55))
|
||||
reqH int = max(0, m.height-constHeightOffset)
|
||||
reqW int = int(float64(m.width) * 0.6)
|
||||
reqH int = m.height
|
||||
|
||||
detW int = max(0, int(float64(m.width)*0.45))
|
||||
detH int = max(0, m.height-constHeightOffset)
|
||||
detW int = int(float64(m.width) * 0.4)
|
||||
detH int = m.height
|
||||
|
||||
eventW int = max(0, m.width)
|
||||
eventH int = max(0, int(float64(m.height)*0.15))
|
||||
eventW int = m.width
|
||||
eventH int = int(float64(m.height) * 0.15)
|
||||
|
||||
stdW int = max(0, m.width)
|
||||
stdH int = max(0, int(float64(m.height)*0.2))
|
||||
stdW int = m.width
|
||||
stdH int = int(float64(m.height) * 0.2)
|
||||
)
|
||||
|
||||
if m.showSearch {
|
||||
reqH = max(0, reqH-searchH)
|
||||
detH = max(0, detH-searchH)
|
||||
reqH -= searchH
|
||||
detH -= searchH
|
||||
}
|
||||
|
||||
if m.showEvents {
|
||||
reqH = max(0, reqH-eventH)
|
||||
detH = max(0, detH-eventH)
|
||||
reqH -= eventH
|
||||
detH -= eventH
|
||||
}
|
||||
|
||||
if m.showStd {
|
||||
reqH = max(0, reqH-stdH)
|
||||
detH = max(0, detH-stdH)
|
||||
reqH -= stdH
|
||||
detH -= stdH
|
||||
}
|
||||
|
||||
reqPane := m.renderRequestPane(reqW, reqH)
|
||||
@ -46,9 +43,6 @@ func (m Model) renderAppPane() string {
|
||||
}
|
||||
|
||||
var screen []string
|
||||
statusBar := m.renderStatusBar(m.width)
|
||||
screen = append(screen, statusBar)
|
||||
|
||||
if m.showSearch {
|
||||
searchPane := m.renderSearchPane(searchW, searchH)
|
||||
screen = append(screen, searchPane...)
|
||||
@ -74,3 +68,43 @@ func (m Model) renderAppPane() string {
|
||||
|
||||
return strings.Join(screen, ("\n"))
|
||||
}
|
||||
|
||||
func (m Model) renderSearchPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat(" ", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderRequestPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat(".", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderDetailsPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat("^", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderEventsPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat("~", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func (m Model) renderStdPane(w, h int) []string {
|
||||
lines := make([]string, h)
|
||||
for y := range lines {
|
||||
lines[y] = strings.Repeat(" ", w)
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"termtap.dev/internal/model"
|
||||
@ -15,13 +14,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
|
||||
case TickMsg:
|
||||
m.now = msg.Now
|
||||
if m.hasPendingRequests() {
|
||||
return m, tickCmd()
|
||||
}
|
||||
return m, nil
|
||||
|
||||
// TODO: Abstract the keymaps
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
@ -40,7 +32,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case ErrMsg:
|
||||
m.events = append(m.events, model.Event{
|
||||
Time: time.Now().Local(),
|
||||
Type: model.EventTypeWarn,
|
||||
Body: fmt.Sprintf("tui event stream closed: %v", msg.err),
|
||||
})
|
||||
@ -49,9 +40,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case EventMsg:
|
||||
m.pushEvent(msg.value)
|
||||
m.applyMessage(msg.value)
|
||||
if m.hasPendingRequests() {
|
||||
return m, tea.Batch(waitForEvent(m.channel), tickCmd())
|
||||
}
|
||||
return m, waitForEvent(m.channel)
|
||||
}
|
||||
|
||||
@ -94,14 +82,3 @@ func (m *Model) updateRequest(req model.Request) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) hasPendingRequests() bool {
|
||||
// Traverse backward to be a bit more efficient, the most recent requests are more
|
||||
// like to be pending.
|
||||
for i := len(m.requests) - 1; i >= 0; i-- {
|
||||
if m.requests[i].Pending {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -2,28 +2,95 @@ package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
// TODO: This is all temporary
|
||||
func (m Model) View() string {
|
||||
return m.renderAppPane()
|
||||
|
||||
// eventLines := m.renderEvents(8)
|
||||
// requestLines := m.renderRequests(12)
|
||||
//
|
||||
// return strings.Join([]string{
|
||||
// "termtap - live session",
|
||||
// fmt.Sprintf("events=%d requests=%d", len(m.events), len(m.requests)),
|
||||
// fmt.Sprintf("%dx%d", m.height, m.width),
|
||||
// "keys: q/esc/ctrl+c quit",
|
||||
// "",
|
||||
// "Recent events:",
|
||||
// eventLines,
|
||||
// "",
|
||||
// "Recent requests:",
|
||||
// requestLines,
|
||||
// }, "\n")
|
||||
}
|
||||
|
||||
func formatDuration(d time.Duration) string {
|
||||
if d == 0 {
|
||||
return "PENDING"
|
||||
func (m Model) renderEvents(limit int) string {
|
||||
if len(m.events) == 0 {
|
||||
return " (none yet)"
|
||||
}
|
||||
|
||||
if d >= 10*time.Second {
|
||||
return fmt.Sprintf("%.2fs", d.Seconds())
|
||||
start := max(len(m.events)-limit, 0)
|
||||
|
||||
rows := make([]string, 0, len(m.events)-start)
|
||||
for i := start; i < len(m.events); i++ {
|
||||
e := m.events[i]
|
||||
rows = append(rows, fmt.Sprintf(" [%s] %s", e.Type, truncate(e.Body, 100)))
|
||||
}
|
||||
|
||||
if d >= time.Millisecond {
|
||||
return fmt.Sprintf("%dms", d.Milliseconds())
|
||||
return strings.Join(rows, "\n")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%dus", d.Microseconds())
|
||||
func (m Model) renderRequests(limit int) string {
|
||||
if len(m.requests) == 0 {
|
||||
return " (none yet)"
|
||||
}
|
||||
|
||||
start := max(0, len(m.requests)-limit)
|
||||
|
||||
// Traverse backwards since we don't have a stack
|
||||
rows := make([]string, 0, len(m.requests)-start)
|
||||
for i := len(m.requests) - 1; i >= start; i-- {
|
||||
req := m.requests[i]
|
||||
|
||||
state := "done"
|
||||
if req.Pending {
|
||||
state = "pending"
|
||||
} else if req.Failed {
|
||||
state = "failed"
|
||||
}
|
||||
|
||||
rows = append(rows, fmt.Sprintf(
|
||||
" %s %s status=%d duration=%s state=%s",
|
||||
req.Method,
|
||||
requestPath(req),
|
||||
req.Status,
|
||||
req.Duration,
|
||||
state,
|
||||
))
|
||||
}
|
||||
|
||||
if len(rows) == 0 {
|
||||
return " (none yet)"
|
||||
}
|
||||
|
||||
return strings.Join(rows, "\n")
|
||||
}
|
||||
|
||||
func requestPath(req model.Request) string {
|
||||
if req.URL != "" {
|
||||
return truncate(req.URL, 80)
|
||||
}
|
||||
if req.RawURL != "" {
|
||||
return truncate(req.RawURL, 80)
|
||||
}
|
||||
if req.Host != "" {
|
||||
return truncate(req.Host, 80)
|
||||
}
|
||||
return "<unknown>"
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user