mirror of
https://github.com/openziti/zrok.git
synced 2024-11-23 16:43:09 +01:00
284 lines
7.3 KiB
Go
284 lines
7.3 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/openziti/zrok/sdk"
|
|
"strings"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/muesli/reflow/wordwrap"
|
|
"github.com/openziti/zrok/endpoints"
|
|
)
|
|
|
|
const shareTuiBacklog = 256
|
|
|
|
var wordwrapCharacters = " -"
|
|
var wordwrapBreakpoints = map[rune]bool{' ': true, '-': true}
|
|
|
|
type shareModel struct {
|
|
shrToken string
|
|
frontendDescriptions []string
|
|
shareMode sdk.ShareMode
|
|
backendMode sdk.BackendMode
|
|
requests []*endpoints.Request
|
|
log []string
|
|
showLog bool
|
|
width int
|
|
height int
|
|
headerHeight int
|
|
prg *tea.Program
|
|
}
|
|
|
|
type shareLogLine string
|
|
|
|
func newShareModel(shrToken string, frontendEndpoints []string, shareMode sdk.ShareMode, backendMode sdk.BackendMode) *shareModel {
|
|
return &shareModel{
|
|
shrToken: shrToken,
|
|
frontendDescriptions: frontendEndpoints,
|
|
shareMode: shareMode,
|
|
backendMode: backendMode,
|
|
}
|
|
}
|
|
|
|
func (m *shareModel) Init() tea.Cmd { return nil }
|
|
|
|
func (m *shareModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
switch msg := msg.(type) {
|
|
case *endpoints.Request:
|
|
m.requests = append(m.requests, msg)
|
|
if len(m.requests) > shareTuiBacklog {
|
|
m.requests = m.requests[1:]
|
|
}
|
|
|
|
case shareLogLine:
|
|
m.showLog = true
|
|
m.adjustPaneHeights()
|
|
|
|
m.log = append(m.log, string(msg))
|
|
if len(m.log) > shareTuiBacklog {
|
|
m.log = m.log[1:]
|
|
}
|
|
|
|
case tea.WindowSizeMsg:
|
|
m.width = msg.Width
|
|
shareHeaderStyle.Width(m.width - 30)
|
|
shareConfigStyle.Width(26)
|
|
shareRequestsStyle.Width(m.width - 2)
|
|
shareLogStyle.Width(m.width - 2)
|
|
|
|
m.height = msg.Height
|
|
m.headerHeight = len(m.frontendDescriptions) + 4
|
|
m.adjustPaneHeights()
|
|
|
|
case tea.KeyMsg:
|
|
switch msg.String() {
|
|
case "ctrl+c", "q":
|
|
return m, tea.Quit
|
|
case "ctrl+l":
|
|
return m, tea.ClearScreen
|
|
case "l":
|
|
m.showLog = !m.showLog
|
|
m.adjustPaneHeights()
|
|
}
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m *shareModel) View() string {
|
|
topRow := lipgloss.JoinHorizontal(lipgloss.Top,
|
|
shareHeaderStyle.Render(strings.Join(m.frontendDescriptions, "\n")),
|
|
shareConfigStyle.Render(m.renderConfig()),
|
|
)
|
|
var panes string
|
|
if m.showLog {
|
|
panes = lipgloss.JoinVertical(lipgloss.Left,
|
|
shareRequestsStyle.Render(m.renderRequests()),
|
|
shareLogStyle.Render(m.renderLog()),
|
|
)
|
|
} else {
|
|
panes = shareRequestsStyle.Render(m.renderRequests())
|
|
}
|
|
return lipgloss.JoinVertical(lipgloss.Left, topRow, panes)
|
|
}
|
|
|
|
func (m *shareModel) adjustPaneHeights() {
|
|
if !m.showLog {
|
|
shareRequestsStyle.Height(m.height - m.headerHeight)
|
|
} else {
|
|
splitHeight := m.height - m.headerHeight
|
|
shareRequestsStyle.Height(splitHeight/2 - 1)
|
|
shareLogStyle.Height(splitHeight/2 - 1)
|
|
}
|
|
}
|
|
|
|
func (m *shareModel) renderConfig() string {
|
|
out := "["
|
|
if m.shareMode == "public" {
|
|
out += shareModePublicStyle.Render(strings.ToUpper(string(m.shareMode)))
|
|
} else {
|
|
out += shareModePrivateStyle.Render(strings.ToUpper(string(m.shareMode)))
|
|
}
|
|
out += "] ["
|
|
if m.backendMode == "proxy" {
|
|
out += backendModeProxyStyle.Render(strings.ToUpper(string(m.backendMode)))
|
|
} else {
|
|
out += backendModeWebStyle.Render(strings.ToUpper(string(m.backendMode)))
|
|
}
|
|
out += "]"
|
|
return out
|
|
}
|
|
|
|
func (m *shareModel) renderRequests() string {
|
|
var requestLines []string
|
|
for _, req := range m.requests {
|
|
reqLine := fmt.Sprintf("%v %v -> %v %v",
|
|
timeStyle.Render(req.Stamp.Format(time.RFC850)),
|
|
addressStyle.Render(req.RemoteAddr),
|
|
m.renderMethod(req.Method),
|
|
req.Path,
|
|
)
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
requestLines = wrap(requestLines, m.width-2)
|
|
maxRows := shareRequestsStyle.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
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (m *shareModel) 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)
|
|
}
|
|
}
|
|
|
|
func (m *shareModel) 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)
|
|
}
|
|
}
|
|
}
|
|
splitLines = wrap(splitLines, m.width-2)
|
|
maxRows := shareLogStyle.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 *shareModel) Write(p []byte) (n int, err error) {
|
|
in := string(p)
|
|
lines := strings.Split(in, "\n")
|
|
for _, line := range lines {
|
|
cleanLine := strings.ReplaceAll(line, "\n", "")
|
|
if cleanLine != "" && m.prg != nil {
|
|
m.prg.Send(shareLogLine(cleanLine))
|
|
}
|
|
}
|
|
return len(p), nil
|
|
}
|
|
|
|
func (shareModel) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func wrap(lines []string, width int) []string {
|
|
ret := make([]string, 0)
|
|
for _, line := range lines {
|
|
if width <= 0 || len(line) <= width {
|
|
ret = append(ret, line)
|
|
continue
|
|
}
|
|
for i := 0; i <= len(line); {
|
|
max := i + width
|
|
if max > len(line) {
|
|
max = len(line)
|
|
}
|
|
if line[i:max] == "" {
|
|
continue
|
|
}
|
|
nextI := i + width
|
|
if max < len(line)-1 {
|
|
if !wordwrapBreakpoints[rune(line[max])] || !wordwrapBreakpoints[rune(line[max+1])] {
|
|
lastSpace := strings.LastIndexAny(line[:max], wordwrapCharacters)
|
|
if lastSpace > -1 {
|
|
max = lastSpace
|
|
nextI = lastSpace
|
|
}
|
|
}
|
|
}
|
|
ret = append(ret, strings.TrimSpace(line[i:max]))
|
|
i = nextI
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
var shareHeaderStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("63")).
|
|
Align(lipgloss.Center)
|
|
|
|
var shareConfigStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("63")).
|
|
Align(lipgloss.Center)
|
|
|
|
var shareRequestsStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("63"))
|
|
|
|
var shareLogStyle = lipgloss.NewStyle().
|
|
BorderStyle(lipgloss.RoundedBorder()).
|
|
BorderForeground(lipgloss.Color("63"))
|
|
|
|
var shareModePublicStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0F0"))
|
|
var shareModePrivateStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#F00"))
|
|
var backendModeProxyStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
|
|
var backendModeWebStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0CC"))
|
|
var timeStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#444"))
|
|
var addressStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500"))
|
|
var getStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("98"))
|
|
var postStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("101"))
|
|
var otherMethodStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("166"))
|