Allow configuring forwards-table columns, order, and width (#82)

* Allow configuring forwards-table columns, order, and width

Adds an optional tui.columns section to .kportal.yaml so the visible
columns, their order, and their widths can be customized instead of
always rendering the hardcoded 8-column layout. Omitting the section
keeps the existing default columns/order/widths.

Closes #81

* fix(ui): keep the plain table layout and check column widths

The plain verbose table must look the same when no tui section is
set. It had narrower columns and shorter port headers than before. The
default layout is now identical to the old one, and a golden test
guards it.

A status column that is not last now pads by visible width. Table
rules follow the column widths. The UI applies new columns only after a
reload succeeds, including the SIGHUP path of the verbose table.

The validator rejects a column width above 200. The README describes
the tui.columns setting.

Refs #81

* fix(httplog): replace the deprecated ReverseProxy Director with Rewrite

Staticcheck reports SA1019 because Go 1.26 deprecates Director. The
proxy now sets the target in a Rewrite function. Rewrite drops the
X-Forwarded-For header that Director sent, so the function calls
SetXForwarded to keep that header.

A new test checks the target port, the path and the header.

* fix(release): drop the deprecated cask url verified key

Homebrew warns about the verified parameter of the cask url stanza.
The setting is not needed. GoReleaser check now passes without a
deprecation warning.

The postflight warning remains. GoReleaser generates it from the
post install hook, and the upstream fix is not released yet.

Refs #79

---------

Co-authored-by: Lukasz Raczylo <[email protected]>
This commit is contained in:
Bruno Costa
2026-10-02 02:24:11 +01:00
committed by GitHub
co-authored by lukaszraczylo
parent 232c0f1162
commit f3d3f259a1
15 changed files with 849 additions and 61 deletions
+34 -15
View File
@@ -72,6 +72,7 @@ type BubbleTeaUI struct {
discovery *k8s.Discovery
program *tea.Program
forwards map[string]*ForwardStatus
columns []ResolvedColumn
benchmarkState *BenchmarkState
httpLogSubscriber HTTPLogSubscriber
disabledMap map[string]bool
@@ -115,11 +116,20 @@ func NewBubbleTeaUI(toggleCallback func(id string, enable bool), version string)
version: version,
errors: make(map[string]string),
viewMode: ViewModeMain,
columns: ResolveColumns(nil),
}
return ui
}
// SetColumns updates the forwards-table column set and order, e.g. after a
// config hot-reload. cfg may be nil to reset to the built-in defaults.
func (ui *BubbleTeaUI) SetColumns(cfg *config.Config) {
ui.mu.Lock()
defer ui.mu.Unlock()
ui.columns = ResolveColumns(cfg)
}
// SetWizardDependencies sets the dependencies needed for the add/remove wizards
func (ui *BubbleTeaUI) SetWizardDependencies(discovery *k8s.Discovery, mutator *config.Mutator, configPath string) {
ui.mu.Lock()
@@ -545,10 +555,15 @@ func (m model) renderForwardsTable(colors mainViewColors) string {
// Build table rows
rows := m.buildTableRows()
headers := make([]string, len(m.ui.columns))
for i, col := range m.ui.columns {
headers[i] = col.Header
}
// Create table with styling (no borders for cleaner look)
t := table.New().
Border(lipgloss.HiddenBorder()).
Headers("CONTEXT", "NAMESPACE", "ALIAS", "TYPE", "RESOURCE", "REMOTE", "LOCAL", "STATUS").
Headers(headers...).
Rows(rows...).
StyleFunc(m.createTableStyleFunc(colors))
@@ -570,21 +585,23 @@ func (m model) buildTableRows() [][]string {
statusIcon, statusText := m.getStatusIconAndText(id, fwd)
localPortText := fmt.Sprintf("%d", fwd.LocalPort)
if fwd.Status == "Active" && !m.ui.isForwardDisabled(id) {
localPortText = hyperlink(fmt.Sprintf("http://127.0.0.1:%d", fwd.LocalPort), fmt.Sprintf("%d→", fwd.LocalPort))
row := make([]string, len(m.ui.columns))
for i, col := range m.ui.columns {
switch col.Key {
case ColKeyLocal:
localPortText := fmt.Sprintf("%d", fwd.LocalPort)
if fwd.Status == "Active" && !m.ui.isForwardDisabled(id) {
localPortText = hyperlink(fmt.Sprintf("http://127.0.0.1:%d", fwd.LocalPort), fmt.Sprintf("%d→", fwd.LocalPort))
}
row[i] = localPortText
case ColKeyStatus:
row[i] = statusIcon + " " + statusText
default:
row[i] = columnValue(col, fwd)
}
}
rows = append(rows, []string{
truncate(fwd.Context, ColumnWidthContext),
truncate(fwd.Namespace, ColumnWidthNamespace),
truncate(fwd.Alias, ColumnWidthAlias),
truncate(fwd.Type, ColumnWidthType),
truncate(fwd.Resource, ColumnWidthResource),
fmt.Sprintf("%d", fwd.RemotePort),
localPortText,
statusIcon + " " + statusText,
})
rows = append(rows, row)
}
return rows
@@ -613,6 +630,8 @@ func (m model) getStatusIconAndText(id string, fwd *ForwardStatus) (icon, text s
// createTableStyleFunc creates the style function for the forwards table
func (m model) createTableStyleFunc(colors mainViewColors) func(row, col int) lipgloss.Style {
statusCol := columnIndex(m.ui.columns, ColKeyStatus)
return func(row, col int) lipgloss.Style {
// Header row
if row == table.HeaderRow {
@@ -643,7 +662,7 @@ func (m model) createTableStyleFunc(colors mainViewColors) func(row, col int) li
}
// Status column gets colored based on status
if col == ColumnStatus && ok {
if col == statusCol && ok {
switch fwd.Status {
case "Active":
return baseStyle.Foreground(colors.active)