2023-01-10 22:38:53 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
2023-01-11 18:46:04 +01:00
|
|
|
"github.com/muesli/reflow/wordwrap"
|
2023-01-13 21:01:34 +01:00
|
|
|
"github.com/openziti/zrok/endpoints"
|
2023-01-10 22:38:53 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2023-01-11 19:36:48 +01:00
|
|
|
const accessTuiBacklog = 256
|
2023-01-11 18:46:04 +01:00
|
|
|
|
2023-01-10 22:38:53 +01:00
|
|
|
type accessModel struct {
|
|
|
|
shrToken string
|
|
|
|
localEndpoint string
|
2023-01-10 23:40:20 +01:00
|
|
|
requests []*endpoints.Request
|
2023-01-11 18:46:04 +01:00
|
|
|
log []string
|
|
|
|
showLog bool
|
2023-01-10 22:38:53 +01:00
|
|
|
width int
|
|
|
|
height int
|
2023-01-11 18:46:04 +01:00
|
|
|
prg *tea.Program
|
2023-01-10 22:38:53 +01:00
|
|
|
}
|
|
|
|
|
2023-01-11 18:46:04 +01:00
|
|
|
type accessLogLine string
|
|
|
|
|
2023-01-10 22:38:53 +01:00
|
|
|
func newAccessModel(shrToken, localEndpoint string) *accessModel {
|
|
|
|
return &accessModel{
|
|
|
|
shrToken: shrToken,
|
|
|
|
localEndpoint: localEndpoint,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *accessModel) Init() tea.Cmd { return nil }
|
|
|
|
|
|
|
|
func (m *accessModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
|
switch msg := msg.(type) {
|
2023-01-10 23:40:20 +01:00
|
|
|
case *endpoints.Request:
|
2023-01-11 18:46:04 +01:00
|
|
|
m.requests = append(m.requests, msg)
|
2023-01-11 19:36:48 +01:00
|
|
|
if len(m.requests) > accessTuiBacklog {
|
2023-01-11 18:46:04 +01:00
|
|
|
m.requests = m.requests[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
case accessLogLine:
|
|
|
|
m.showLog = true
|
2023-01-11 19:36:48 +01:00
|
|
|
m.adjustPaneHeights()
|
2023-01-11 18:46:04 +01:00
|
|
|
|
|
|
|
m.log = append(m.log, string(msg))
|
2023-01-11 19:36:48 +01:00
|
|
|
if len(m.log) > accessTuiBacklog {
|
2023-01-11 18:46:04 +01:00
|
|
|
m.log = m.log[1:]
|
2023-01-10 22:38:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
case tea.WindowSizeMsg:
|
|
|
|
m.width = msg.Width
|
|
|
|
accessHeaderStyle.Width(m.width - 2)
|
|
|
|
accessRequestsStyle.Width(m.width - 2)
|
2023-01-11 18:46:04 +01:00
|
|
|
accessLogStyle.Width(m.width - 2)
|
2023-01-10 22:38:53 +01:00
|
|
|
|
|
|
|
m.height = msg.Height
|
2023-01-11 19:36:48 +01:00
|
|
|
m.adjustPaneHeights()
|
2023-01-10 22:38:53 +01:00
|
|
|
|
|
|
|
case tea.KeyMsg:
|
|
|
|
switch msg.String() {
|
|
|
|
case "ctrl+c", "q":
|
|
|
|
return m, tea.Quit
|
|
|
|
case "ctrl+l":
|
|
|
|
return m, tea.ClearScreen
|
2023-01-11 18:46:04 +01:00
|
|
|
case "l":
|
|
|
|
m.showLog = !m.showLog
|
2023-01-11 19:36:48 +01:00
|
|
|
m.adjustPaneHeights()
|
2023-01-10 22:38:53 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *accessModel) View() string {
|
2023-01-11 18:46:04 +01:00
|
|
|
var panes string
|
|
|
|
if m.showLog {
|
|
|
|
panes = lipgloss.JoinVertical(lipgloss.Left,
|
|
|
|
accessRequestsStyle.Render(m.renderRequests()),
|
|
|
|
accessLogStyle.Render(m.renderLog()),
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
panes = accessRequestsStyle.Render(m.renderRequests())
|
|
|
|
}
|
|
|
|
|
2023-01-10 22:38:53 +01:00
|
|
|
return lipgloss.JoinVertical(
|
|
|
|
lipgloss.Left,
|
|
|
|
accessHeaderStyle.Render(fmt.Sprintf("%v -> %v", m.localEndpoint, m.shrToken)),
|
2023-01-11 18:46:04 +01:00
|
|
|
panes,
|
2023-01-10 22:38:53 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2023-01-11 19:36:48 +01:00
|
|
|
func (m *accessModel) adjustPaneHeights() {
|
|
|
|
if !m.showLog {
|
|
|
|
accessRequestsStyle.Height(m.height - 5)
|
|
|
|
} else {
|
|
|
|
splitHeight := m.height - 5
|
|
|
|
accessRequestsStyle.Height(splitHeight/2 - 1)
|
|
|
|
accessLogStyle.Height(splitHeight/2 - 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-10 22:38:53 +01:00
|
|
|
func (m *accessModel) renderRequests() string {
|
2023-01-11 18:46:04 +01:00
|
|
|
var requestLines []string
|
|
|
|
for _, req := range m.requests {
|
|
|
|
reqLine := fmt.Sprintf("%v %v -> %v %v",
|
2023-01-10 22:38:53 +01:00
|
|
|
timeStyle.Render(req.Stamp.Format(time.RFC850)),
|
|
|
|
addressStyle.Render(req.RemoteAddr),
|
|
|
|
m.renderMethod(req.Method),
|
|
|
|
req.Path,
|
|
|
|
)
|
2023-01-11 18:46:04 +01:00
|
|
|
reqLineWrapped := wordwrap.String(reqLine, m.width-2)
|
|
|
|
splitWrapped := strings.Split(reqLineWrapped, "\n")
|
|
|
|
for _, splitLine := range splitWrapped {
|
|
|
|
splitLine := strings.ReplaceAll(splitLine, "\n", "")
|
|
|
|
if splitLine != "" {
|
|
|
|
requestLines = append(requestLines, splitLine)
|
|
|
|
}
|
2023-01-10 22:38:53 +01:00
|
|
|
}
|
|
|
|
}
|
2023-01-11 18:46:04 +01:00
|
|
|
maxRows := accessRequestsStyle.GetHeight()
|
|
|
|
startRow := 0
|
|
|
|
if len(requestLines) > maxRows {
|
|
|
|
startRow = len(requestLines) - maxRows
|
|
|
|
}
|
|
|
|
out := ""
|
|
|
|
for i := startRow; i < len(requestLines); i++ {
|
|
|
|
outLine := requestLines[i]
|
|
|
|
if i < len(requestLines)-1 {
|
|
|
|
outLine += "\n"
|
|
|
|
}
|
|
|
|
out += outLine
|
|
|
|
}
|
2023-01-10 22:38:53 +01:00
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *accessModel) renderMethod(method string) string {
|
|
|
|
switch strings.ToLower(method) {
|
|
|
|
case "get":
|
|
|
|
return getStyle.Render(method)
|
|
|
|
case "post":
|
|
|
|
return postStyle.Render(method)
|
|
|
|
default:
|
|
|
|
return otherMethodStyle.Render(method)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-11 18:46:04 +01:00
|
|
|
func (m *accessModel) renderLog() string {
|
|
|
|
var splitLines []string
|
|
|
|
for _, line := range m.log {
|
|
|
|
wrapped := wordwrap.String(line, m.width-2)
|
|
|
|
wrappedLines := strings.Split(wrapped, "\n")
|
|
|
|
for _, wrappedLine := range wrappedLines {
|
|
|
|
splitLine := strings.ReplaceAll(wrappedLine, "\n", "")
|
|
|
|
if splitLine != "" {
|
|
|
|
splitLines = append(splitLines, splitLine)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
maxRows := accessLogStyle.GetHeight()
|
|
|
|
startRow := 0
|
|
|
|
if len(splitLines) > maxRows {
|
|
|
|
startRow = len(splitLines) - maxRows
|
|
|
|
}
|
|
|
|
out := ""
|
|
|
|
for i := startRow; i < len(splitLines); i++ {
|
|
|
|
outLine := splitLines[i]
|
|
|
|
if i < len(splitLines)-1 {
|
|
|
|
outLine += "\n"
|
|
|
|
}
|
|
|
|
out += outLine
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *accessModel) Write(p []byte) (n int, err error) {
|
|
|
|
in := string(p)
|
|
|
|
lines := strings.Split(in, "\n")
|
|
|
|
for _, line := range lines {
|
|
|
|
cleanLine := strings.ReplaceAll(line, "\n", "")
|
2023-01-24 17:50:43 +01:00
|
|
|
if cleanLine != "" && m.prg != nil {
|
2023-01-11 18:46:04 +01:00
|
|
|
m.prg.Send(accessLogLine(cleanLine))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(p), nil
|
|
|
|
}
|
|
|
|
|
2023-01-10 22:38:53 +01:00
|
|
|
var accessHeaderStyle = lipgloss.NewStyle().
|
|
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
|
|
BorderForeground(lipgloss.Color("63")).
|
|
|
|
Align(lipgloss.Center)
|
|
|
|
|
|
|
|
var accessRequestsStyle = lipgloss.NewStyle().
|
|
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
|
|
BorderForeground(lipgloss.Color("63"))
|
|
|
|
|
2023-01-11 18:46:04 +01:00
|
|
|
var accessLogStyle = lipgloss.NewStyle().
|
|
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
|
|
BorderForeground(lipgloss.Color("63"))
|