WIP: working on tui and process killing
This commit is contained in:
parent
24b00146bf
commit
58da1e3a64
22
go.mod
22
go.mod
@ -3,3 +3,25 @@ module termtap.dev
|
||||
go 1.26.1
|
||||
|
||||
require github.com/google/uuid v1.6.0
|
||||
|
||||
require (
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/charmbracelet/bubbletea v1.3.10 // indirect
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||
github.com/charmbracelet/lipgloss v1.1.0 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect
|
||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/muesli/termenv v0.16.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/text v0.3.8 // indirect
|
||||
)
|
||||
|
||||
41
go.sum
41
go.sum
@ -1,2 +1,43 @@
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8=
|
||||
github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
|
||||
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
"termtap.dev/internal/process"
|
||||
@ -38,7 +40,15 @@ func StartProcess(cmd model.Command, addr string, ch chan<- model.Message, sigCh
|
||||
}
|
||||
|
||||
if proc.Exec != nil {
|
||||
_ = proc.Exec.Process.Signal(sig)
|
||||
_ = process.SignalProcess(proc.Exec, sig)
|
||||
|
||||
go func() {
|
||||
time.Sleep(1500 * time.Millisecond)
|
||||
if process.ProcessAlive(proc.Exec) {
|
||||
_ = process.SignalProcess(proc.Exec, syscall.SIGKILL)
|
||||
}
|
||||
}()
|
||||
|
||||
process.UpdateStatus(proc, false, ch)
|
||||
}
|
||||
}()
|
||||
|
||||
@ -1,81 +1,46 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
func StartSession(cmd model.Command, addr string) error {
|
||||
type Session struct {
|
||||
Messages <-chan model.Message
|
||||
|
||||
// Event type?
|
||||
msgs := make(chan model.Message, 128)
|
||||
sigCh chan os.Signal
|
||||
stopOnce sync.Once
|
||||
}
|
||||
|
||||
func StartSession(cmd model.Command, addr string) (*Session, error) {
|
||||
msgs := make(chan model.Message, 256)
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
|
||||
defer signal.Stop(sigCh)
|
||||
|
||||
// Start process and proxy
|
||||
go StartProxy(addr, msgs)
|
||||
go StartProcess(cmd, addr, msgs, sigCh)
|
||||
|
||||
var events []model.Message
|
||||
return &Session{
|
||||
Messages: msgs,
|
||||
sigCh: sigCh,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var requests []model.Request
|
||||
func (s *Session) Stop() {
|
||||
if s == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.stopOnce.Do(func() {
|
||||
signal.Stop(s.sigCh)
|
||||
|
||||
for {
|
||||
select {
|
||||
case _ = <-sigCh:
|
||||
fmt.Println("\n\nEVENTS")
|
||||
printEvents(events)
|
||||
fmt.Println("\n\nREQUESTS")
|
||||
printRequests(requests)
|
||||
return nil
|
||||
case msg := <-msgs:
|
||||
{
|
||||
events = append(events, msg)
|
||||
switch msg.Type {
|
||||
case model.MessageTypeFatal:
|
||||
return fmt.Errorf("%s", msg.Body)
|
||||
|
||||
case model.MessageTypeRequestStarted:
|
||||
log.Printf("[%s] (%s) %s", msg.Type, msg.Request.ID.String(), msg.Body)
|
||||
requests = append(requests, msg.Request)
|
||||
|
||||
case model.MessageTypeRequestFinished, model.MessageTypeRequestFailed:
|
||||
log.Printf("[%s] (%s) %s", msg.Type, msg.Request.ID.String(), msg.Body)
|
||||
|
||||
for i := range requests {
|
||||
if requests[i].ID == msg.Request.ID {
|
||||
requests[i] = msg.Request
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
log.Printf("[%s] %s", msg.Type, msg.Body)
|
||||
}
|
||||
}
|
||||
case s.sigCh <- syscall.SIGTERM:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG
|
||||
func printEvents(events []model.Message) {
|
||||
for _, event := range events {
|
||||
fmt.Printf("%+v\n", event)
|
||||
}
|
||||
}
|
||||
|
||||
func printRequests(reqs []model.Request) {
|
||||
for _, req := range reqs {
|
||||
fmt.Printf("%+v\n", req)
|
||||
for k, v := range req.QueryMap {
|
||||
fmt.Printf("key: %s, vals: %+v\n", k, v)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"termtap.dev/internal/app"
|
||||
"termtap.dev/internal/model"
|
||||
"termtap.dev/internal/tui"
|
||||
)
|
||||
|
||||
// This should be configurable at some point, just in case they build on 8080
|
||||
@ -19,10 +20,15 @@ func Run(args []string) {
|
||||
return
|
||||
}
|
||||
|
||||
err := app.StartSession(cmd, proxy_addr)
|
||||
session, err := app.StartSession(cmd, proxy_addr)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
defer session.Stop()
|
||||
|
||||
if err := tui.Run(session.Messages); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseCommand(args []string) (model.Command, bool) {
|
||||
|
||||
@ -17,6 +17,7 @@ func CommandString(c model.Command) string {
|
||||
|
||||
func NewProcess(cmd model.Command, addr string, ch chan<- model.Message) *model.Process {
|
||||
proc := exec.Command(cmd.Name, cmd.Args...)
|
||||
configureProcessForSignals(proc)
|
||||
|
||||
injectEnv(proc, addr)
|
||||
|
||||
|
||||
32
internal/process/signal_nonunix.go
Normal file
32
internal/process/signal_nonunix.go
Normal file
@ -0,0 +1,32 @@
|
||||
//go:build !unix
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func configureProcessForSignals(cmd *exec.Cmd) {
|
||||
_ = cmd
|
||||
}
|
||||
|
||||
func SignalProcess(cmd *exec.Cmd, sig os.Signal) error {
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Process.Signal(sig)
|
||||
}
|
||||
|
||||
func ProcessAlive(cmd *exec.Cmd) bool {
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if cmd.ProcessState == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
return !cmd.ProcessState.Exited()
|
||||
}
|
||||
46
internal/process/signal_unix.go
Normal file
46
internal/process/signal_unix.go
Normal file
@ -0,0 +1,46 @@
|
||||
//go:build unix
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func configureProcessForSignals(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
}
|
||||
|
||||
func SignalProcess(cmd *exec.Cmd, sig os.Signal) error {
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pid := cmd.Process.Pid
|
||||
if pid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sysSig, ok := sig.(syscall.Signal)
|
||||
if !ok {
|
||||
return cmd.Process.Signal(sig)
|
||||
}
|
||||
|
||||
err := syscall.Kill(-pid, sysSig)
|
||||
if err == nil || errors.Is(err, syscall.ESRCH) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return cmd.Process.Signal(sig)
|
||||
}
|
||||
|
||||
func ProcessAlive(cmd *exec.Cmd) bool {
|
||||
if cmd == nil || cmd.Process == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
err := syscall.Kill(-cmd.Process.Pid, 0)
|
||||
return err == nil || errors.Is(err, syscall.EPERM)
|
||||
}
|
||||
@ -1,2 +1,65 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/google/uuid"
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
const (
|
||||
maxEvents = 200
|
||||
maxRequests = 200
|
||||
)
|
||||
|
||||
type appMsg struct {
|
||||
value model.Message
|
||||
}
|
||||
|
||||
type modelErrMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type Model struct {
|
||||
msgCh <-chan model.Message
|
||||
|
||||
events []model.Message
|
||||
requestOrder []uuid.UUID
|
||||
requests map[uuid.UUID]model.Request
|
||||
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
func NewModel(msgCh <-chan model.Message) Model {
|
||||
return Model{
|
||||
msgCh: msgCh,
|
||||
events: make([]model.Message, 0, maxEvents),
|
||||
requestOrder: make([]uuid.UUID, 0, maxRequests),
|
||||
requests: map[uuid.UUID]model.Request{},
|
||||
width: 100,
|
||||
height: 28,
|
||||
}
|
||||
}
|
||||
|
||||
func Run(msgCh <-chan model.Message) error {
|
||||
p := tea.NewProgram(NewModel(msgCh), tea.WithAltScreen())
|
||||
_, err := p.Run()
|
||||
return err
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return waitForAppMessage(m.msgCh)
|
||||
}
|
||||
|
||||
func waitForAppMessage(msgCh <-chan model.Message) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
msg, ok := <-msgCh
|
||||
if !ok {
|
||||
return modelErrMsg{err: fmt.Errorf("event channel closed")}
|
||||
}
|
||||
|
||||
return appMsg{value: msg}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,77 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/google/uuid"
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
return m, nil
|
||||
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "q", "esc":
|
||||
return m, tea.Quit
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case modelErrMsg:
|
||||
m.events = append(m.events, model.Message{
|
||||
Type: model.MessageTypeWarn,
|
||||
Body: fmt.Sprintf("tui event stream closed: %v", msg.err),
|
||||
})
|
||||
return m, nil
|
||||
|
||||
case appMsg:
|
||||
m.pushEvent(msg.value)
|
||||
m.applyMessage(msg.value)
|
||||
return m, waitForAppMessage(m.msgCh)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Model) pushEvent(msg model.Message) {
|
||||
m.events = append(m.events, msg)
|
||||
if len(m.events) > maxEvents {
|
||||
m.events = m.events[len(m.events)-maxEvents:]
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) applyMessage(msg model.Message) {
|
||||
switch msg.Type {
|
||||
case model.MessageTypeRequestStarted:
|
||||
m.upsertRequest(msg.Request, true)
|
||||
case model.MessageTypeRequestFinished, model.MessageTypeRequestFailed:
|
||||
m.upsertRequest(msg.Request, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) upsertRequest(req model.Request, addIfMissing bool) {
|
||||
if req.ID == uuid.Nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, exists := m.requests[req.ID]
|
||||
if !exists && !addIfMissing {
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
m.requestOrder = append(m.requestOrder, req.ID)
|
||||
if len(m.requestOrder) > maxRequests {
|
||||
drop := m.requestOrder[0]
|
||||
delete(m.requests, drop)
|
||||
m.requestOrder = m.requestOrder[1:]
|
||||
}
|
||||
}
|
||||
|
||||
m.requests[req.ID] = req
|
||||
}
|
||||
|
||||
@ -1,2 +1,111 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"termtap.dev/internal/model"
|
||||
)
|
||||
|
||||
func (m Model) View() string {
|
||||
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.requestOrder)),
|
||||
"keys: q/esc/ctrl+c quit",
|
||||
"",
|
||||
"Recent events:",
|
||||
eventLines,
|
||||
"",
|
||||
"Recent requests:",
|
||||
requestLines,
|
||||
}, "\n")
|
||||
}
|
||||
|
||||
func (m Model) renderEvents(limit int) string {
|
||||
if len(m.events) == 0 {
|
||||
return " (none yet)"
|
||||
}
|
||||
|
||||
start := len(m.events) - limit
|
||||
if start < 0 {
|
||||
start = 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)))
|
||||
}
|
||||
|
||||
return strings.Join(rows, "\n")
|
||||
}
|
||||
|
||||
func (m Model) renderRequests(limit int) string {
|
||||
if len(m.requestOrder) == 0 {
|
||||
return " (none yet)"
|
||||
}
|
||||
|
||||
start := len(m.requestOrder) - limit
|
||||
if start < 0 {
|
||||
start = 0
|
||||
}
|
||||
|
||||
rows := make([]string, 0, len(m.requestOrder)-start)
|
||||
for i := start; i < len(m.requestOrder); i++ {
|
||||
id := m.requestOrder[i]
|
||||
req, ok := m.requests[id]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(s) <= max {
|
||||
return s
|
||||
}
|
||||
|
||||
if max <= 3 {
|
||||
return s[:max]
|
||||
}
|
||||
|
||||
return s[:max-3] + "..."
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user