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(), ) }