121 lines
3.2 KiB
Go
121 lines
3.2 KiB
Go
package tui
|
||
|
||
import (
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/charmbracelet/bubbles/viewport"
|
||
tea "github.com/charmbracelet/bubbletea"
|
||
|
||
"github.com/r3g1tpr0cs/burterm/internal/proxy"
|
||
)
|
||
|
||
// proxyModel — список перехваченных прокси-транзакций с курсором выбора.
|
||
// Enter на выбранной строке отправляет её сырой запрос в Request Builder
|
||
// (через loadEntryMsg, обрабатывается в корневой модели).
|
||
type proxyModel struct {
|
||
entries []proxy.Entry
|
||
cursor int
|
||
viewport viewport.Model
|
||
}
|
||
|
||
func newProxyView() proxyModel {
|
||
return proxyModel{viewport: viewport.New(0, 0)}
|
||
}
|
||
|
||
func (m *proxyModel) SetSize(width, height int) {
|
||
if width < 0 {
|
||
width = 0
|
||
}
|
||
if height < 0 {
|
||
height = 0
|
||
}
|
||
m.viewport.Width = width
|
||
// 6 строк вокруг viewport, которые View() добавляет сам: "Перехвачено:
|
||
// N" + пустая строка сверху, пустая строка + две строки подсказки
|
||
// снизу (футер теперь гарантированно на двух строках, не на одной).
|
||
vh := height - 6
|
||
if vh < 3 {
|
||
vh = 3
|
||
}
|
||
m.viewport.Height = vh
|
||
m.refresh()
|
||
}
|
||
|
||
// AddEntry дописывает новую перехваченную транзакцию в список.
|
||
func (m *proxyModel) AddEntry(e proxy.Entry) {
|
||
m.entries = append(m.entries, e)
|
||
m.refresh()
|
||
}
|
||
|
||
func (m *proxyModel) refresh() {
|
||
if len(m.entries) == 0 {
|
||
m.viewport.SetContent("Пока пусто. Направь браузер на прокси-адрес и походи по нужному сайту.")
|
||
return
|
||
}
|
||
|
||
var b strings.Builder
|
||
for i, e := range m.entries {
|
||
marker := " "
|
||
if i == m.cursor {
|
||
marker = "▸ "
|
||
}
|
||
|
||
ct := contentTypeLabel(e.Headers)
|
||
if ct == "" {
|
||
ct = "-"
|
||
}
|
||
|
||
var status string
|
||
if e.Err != nil {
|
||
status = styleErrStatus.Render(fmt.Sprintf("%-4s", "ERR"))
|
||
} else {
|
||
status = statusStyle(e.StatusCode).Render(fmt.Sprintf("%-4d", e.StatusCode))
|
||
}
|
||
|
||
b.WriteString(fmt.Sprintf("%s#%-4d %-6s %s %-6s %s\n", marker, e.ID, e.Method, status, ct, e.URL))
|
||
}
|
||
m.viewport.SetContent(b.String())
|
||
}
|
||
|
||
func (m proxyModel) Update(msg tea.Msg) (proxyModel, tea.Cmd) {
|
||
if key, ok := msg.(tea.KeyMsg); ok {
|
||
switch key.String() {
|
||
case "up", "k":
|
||
if m.cursor > 0 {
|
||
m.cursor--
|
||
m.refresh()
|
||
}
|
||
return m, nil
|
||
case "down", "j":
|
||
if m.cursor < len(m.entries)-1 {
|
||
m.cursor++
|
||
m.refresh()
|
||
}
|
||
return m, nil
|
||
case "enter":
|
||
if m.cursor >= 0 && m.cursor < len(m.entries) {
|
||
entry := m.entries[m.cursor]
|
||
return m, func() tea.Msg { return loadEntryMsg{entry: entry, target: targetBuilder} }
|
||
}
|
||
return m, nil
|
||
case "s":
|
||
if m.cursor >= 0 && m.cursor < len(m.entries) {
|
||
entry := m.entries[m.cursor]
|
||
return m, func() tea.Msg { return loadEntryMsg{entry: entry, target: targetRepeater} }
|
||
}
|
||
return m, nil
|
||
}
|
||
}
|
||
|
||
var cmd tea.Cmd
|
||
m.viewport, cmd = m.viewport.Update(msg)
|
||
return m, cmd
|
||
}
|
||
|
||
func (m proxyModel) View() string {
|
||
return fmt.Sprintf(
|
||
"Перехвачено: %d\n\n%s\n\n↑/↓ — выбор · enter — в Request Builder · s — в Repeater\nf1-f12, pgup — вкладки",
|
||
len(m.entries), m.viewport.View(),
|
||
)
|
||
}
|