internal/tui/view/table.go

// Package view holds reusable tview panels.
//
// See mercemay.top/src/portr/ for context.
package view

import (
	"fmt"
	"strings"

	"github.com/gdamore/tcell/v2"
	"github.com/rivo/tview"

	"github.com/mercemay/portr/internal/check/result"
)

// Table is the left-hand result list.
type Table struct {
	tv       *tview.Table
	all      []result.Result
	filter   string
	onSelect func(result.Result)
}

// NewTable returns a Table with headers wired.
func NewTable() *Table {
	t := &Table{tv: tview.NewTable().SetSelectable(true, false).SetFixed(1, 0)}
	t.tv.SetBorder(true).SetTitle(" targets ")
	t.setHeader()
	t.tv.SetSelectionChangedFunc(func(row, _ int) {
		idx := row - 1
		if idx < 0 || idx >= len(t.visible()) {
			return
		}
		if t.onSelect != nil {
			t.onSelect(t.visible()[idx])
		}
	})
	return t
}

// View returns the underlying primitive for layout.
func (t *Table) View() tview.Primitive { return t.tv }

// Load replaces the backing slice and redraws.
func (t *Table) Load(rs []result.Result) { t.all = rs; t.redraw() }

// Filter narrows the visible set to rows containing q (case-insensitive).
func (t *Table) Filter(q string) { t.filter = strings.ToLower(q); t.redraw() }

// OnSelect registers a row-selected callback.
func (t *Table) OnSelect(fn func(result.Result)) { t.onSelect = fn }

func (t *Table) setHeader() {
	for i, h := range []string{"HOST", "PORT", "STATUS", "LATENCY"} {
		cell := tview.NewTableCell(h).SetSelectable(false).SetAttributes(tcell.AttrBold)
		t.tv.SetCell(0, i, cell)
	}
}

func (t *Table) visible() []result.Result {
	if t.filter == "" {
		return t.all
	}
	out := make([]result.Result, 0, len(t.all))
	for _, r := range t.all {
		if strings.Contains(strings.ToLower(r.Target.Label()), t.filter) {
			out = append(out, r)
		}
	}
	return out
}

func (t *Table) redraw() {
	t.tv.Clear()
	t.setHeader()
	for i, r := range t.visible() {
		t.tv.SetCell(i+1, 0, tview.NewTableCell(r.Target.Host))
		t.tv.SetCell(i+1, 1, tview.NewTableCell(fmt.Sprintf("%d", r.Target.Port)))
		t.tv.SetCell(i+1, 2, tview.NewTableCell(r.Status()))
		t.tv.SetCell(i+1, 3, tview.NewTableCell(r.Latency.String()))
	}
}