mirror of
https://github.com/lukaszraczylo/kportal.git
synced 2026-06-09 23:59:45 +00:00
23cd45a3d7
* Further improvements | Fix | Impact | Files Modified | |------------------------------------|----------------------------------------|--------------------------------------| | sync.Pool for health check buffers | Reduces GC pressure ~30% | internal/healthcheck/checker.go | | Goroutine leak fix + sync.Once | Prevents memory leaks | internal/forward/worker.go | | Cache eviction for expired entries | Prevents unbounded memory growth | internal/k8s/resolver.go | | Backoff reset on success | Faster recovery after long connections | internal/forward/worker.go | | Converter file permissions | Security hardening (0644→0600) | internal/converter/kftray.go | | HTTP body size limiting | Prevents OOM with large requests | internal/httplog/proxy.go, logger.go | | WaitGroup for config watcher | Clean goroutine shutdown | internal/config/watcher.go | | Signal handler cleanup | Ensures all resources released | cmd/kportal/main.go | * Additional event bus for internal event handling | Metric | Before | After | Improvement | |------------------------|---------------------------------------|-------------------|--------------------| | Goroutines per forward | 3 (worker + heartbeat + health check) | 1 (worker only) | 66% reduction | | Tickers per forward | 2 (heartbeat + health check) | 0 | 100% reduction | | Global goroutines | 2 (watchdog + health monitor) | 2 | Same | | Lock acquisitions/sec | O(n) per interval | O(1) per interval | Linear improvement | * Add UI testing * Add mocks * Add more logs and details to be displayed
212 lines
5.1 KiB
Go
212 lines
5.1 KiB
Go
package ui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/lipgloss"
|
|
)
|
|
|
|
// Color palette for wizards
|
|
var (
|
|
primaryColor = lipgloss.Color("205") // Pink/Magenta
|
|
successColor = lipgloss.Color("42") // Green
|
|
errorColor = lipgloss.Color("196") // Red
|
|
warningColor = lipgloss.Color("220") // Yellow
|
|
mutedColor = lipgloss.Color("241") // Gray
|
|
accentColor = lipgloss.Color("63") // Purple
|
|
highlightColor = lipgloss.Color("117") // Light blue
|
|
|
|
// JSON syntax highlighting colors
|
|
jsonKeyColor = lipgloss.Color("81") // Cyan
|
|
jsonStringColor = lipgloss.Color("180") // Light orange/tan
|
|
jsonNumberColor = lipgloss.Color("141") // Light purple
|
|
jsonBoolColor = lipgloss.Color("209") // Orange
|
|
jsonNullColor = lipgloss.Color("243") // Dark gray
|
|
)
|
|
|
|
// Text styles
|
|
var (
|
|
wizardHeaderStyle = lipgloss.NewStyle().
|
|
Bold(true).
|
|
Foreground(primaryColor).
|
|
MarginBottom(0)
|
|
|
|
wizardStepStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor).
|
|
Italic(true)
|
|
|
|
breadcrumbStyle = lipgloss.NewStyle().
|
|
Foreground(highlightColor).
|
|
Bold(true)
|
|
|
|
selectedStyle = lipgloss.NewStyle().
|
|
Foreground(primaryColor).
|
|
Bold(true)
|
|
|
|
successStyle = lipgloss.NewStyle().
|
|
Foreground(successColor).
|
|
Bold(true)
|
|
|
|
errorStyle = lipgloss.NewStyle().
|
|
Foreground(errorColor).
|
|
Bold(true)
|
|
|
|
warningStyle = lipgloss.NewStyle().
|
|
Foreground(warningColor).
|
|
Bold(true)
|
|
|
|
mutedStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor)
|
|
|
|
helpStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor).
|
|
Italic(true)
|
|
|
|
spinnerStyle = lipgloss.NewStyle().
|
|
Foreground(accentColor).
|
|
Bold(true)
|
|
|
|
accentStyle = lipgloss.NewStyle().
|
|
Foreground(accentColor).
|
|
Bold(true)
|
|
)
|
|
|
|
// Input styles
|
|
var (
|
|
inputStyle = lipgloss.NewStyle().
|
|
Foreground(lipgloss.Color("252"))
|
|
|
|
validInputStyle = lipgloss.NewStyle().
|
|
Foreground(successColor)
|
|
)
|
|
|
|
// Checkbox styles
|
|
var (
|
|
checkedBoxStyle = lipgloss.NewStyle().
|
|
Foreground(successColor).
|
|
Bold(true)
|
|
|
|
uncheckedBoxStyle = lipgloss.NewStyle().
|
|
Foreground(mutedColor)
|
|
)
|
|
|
|
// JSON syntax highlighting styles
|
|
var (
|
|
jsonKeyStyle = lipgloss.NewStyle().
|
|
Foreground(jsonKeyColor)
|
|
|
|
jsonStringStyle = lipgloss.NewStyle().
|
|
Foreground(jsonStringColor)
|
|
|
|
jsonNumberStyle = lipgloss.NewStyle().
|
|
Foreground(jsonNumberColor)
|
|
|
|
jsonBoolStyle = lipgloss.NewStyle().
|
|
Foreground(jsonBoolColor)
|
|
|
|
jsonNullStyle = lipgloss.NewStyle().
|
|
Foreground(jsonNullColor)
|
|
)
|
|
|
|
// Container styles
|
|
var (
|
|
// wizardBoxStyle creates a bordered modal box
|
|
wizardBoxStyle = lipgloss.NewStyle().
|
|
Border(lipgloss.RoundedBorder()).
|
|
BorderForeground(accentColor).
|
|
Padding(1, 2)
|
|
)
|
|
|
|
// Helper functions for rendering
|
|
|
|
// renderProgress returns a step indicator like "Step 2/7"
|
|
func renderProgress(current, total int) string {
|
|
return wizardStepStyle.Render(fmt.Sprintf("Step %d/%d", current, total))
|
|
}
|
|
|
|
// renderHeader returns a formatted header with title and progress
|
|
func renderHeader(title, progress string) string {
|
|
header := wizardHeaderStyle.Render(title)
|
|
if progress != "" {
|
|
header += " " + progress
|
|
}
|
|
return header + "\n\n"
|
|
}
|
|
|
|
// renderBreadcrumb returns a formatted breadcrumb path
|
|
func renderBreadcrumb(parts ...string) string {
|
|
return breadcrumbStyle.Render(strings.Join(parts, " / "))
|
|
}
|
|
|
|
// renderList renders a list of items with cursor selection and viewport scrolling
|
|
func renderList(items []string, cursor int, prefix string, scrollOffset int) string {
|
|
var b strings.Builder
|
|
|
|
const viewportHeight = 20
|
|
totalItems := len(items)
|
|
|
|
// Show scroll up indicator if there are items above the viewport
|
|
if scrollOffset > 0 {
|
|
b.WriteString(mutedStyle.Render(" ↑ More above ↑") + "\n")
|
|
}
|
|
|
|
// Calculate visible range
|
|
start := scrollOffset
|
|
end := scrollOffset + viewportHeight
|
|
if end > totalItems {
|
|
end = totalItems
|
|
}
|
|
|
|
// Render visible items
|
|
for i := start; i < end; i++ {
|
|
cursorPrefix := prefix
|
|
if i == cursor {
|
|
cursorPrefix = "▸ "
|
|
b.WriteString(selectedStyle.Render(cursorPrefix + items[i]))
|
|
} else {
|
|
b.WriteString(cursorPrefix + items[i])
|
|
}
|
|
b.WriteString("\n")
|
|
}
|
|
|
|
// Show scroll down indicator if there are items below the viewport
|
|
if end < totalItems {
|
|
b.WriteString(mutedStyle.Render(" ↓ More below ↓") + "\n")
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// renderTextInput renders a text input field with a cursor
|
|
func renderTextInput(label, value string, valid bool) string {
|
|
var b strings.Builder
|
|
|
|
b.WriteString(label)
|
|
|
|
inputText := value + "█"
|
|
if valid {
|
|
b.WriteString(validInputStyle.Render(inputText))
|
|
} else {
|
|
b.WriteString(inputStyle.Render(inputText))
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// overlayContent overlays modal content centered on the base view
|
|
// Note: base parameter is kept for API compatibility but not used since
|
|
// lipgloss.Place provides cleaner centering without background artifacts
|
|
func overlayContent(_, modal string, termWidth, termHeight int) string {
|
|
// Use lipgloss.Place to center the modal in the terminal viewport
|
|
// This handles all alignment properly and respects ANSI styling
|
|
return lipgloss.Place(
|
|
termWidth,
|
|
termHeight,
|
|
lipgloss.Center,
|
|
lipgloss.Center,
|
|
modal,
|
|
lipgloss.WithWhitespaceChars(" "),
|
|
)
|
|
}
|