feat: implement ControlTower TUI for cluster and host monitoring
Add complete TUI application for monitoring Kubernetes clusters and host systems. Features include: Core features: - Collector framework with concurrent scheduling - Host collectors: disk, memory, load, network - Kubernetes collectors: pods, nodes, workloads, events with informers - Issue deduplication, state management, and resolve-after logic - Bubble Tea TUI with table view, details pane, and filtering - JSON export functionality UX improvements: - Help overlay with keybindings - Priority/category filters with visual indicators - Direct priority jump (0/1/2/3) - Bulk acknowledge (Shift+A) - Clipboard copy (y) - Theme toggle (T) - Age format toggle (d) - Wide title toggle (t) - Vi-style navigation (j/k) - Home/End jump (g/G) - Rollup drill-down in details Robustness: - Grace period for unreachable clusters - Rollups for high-volume issues - Flap suppression - RBAC error handling Files: All core application code with tests for host collectors, engine, store, model, and export packages.
This commit is contained in:
152
internal/ui/help.go
Normal file
152
internal/ui/help.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
)
|
||||
|
||||
// HelpModel is the help overlay model.
|
||||
type HelpModel struct {
|
||||
visible bool
|
||||
}
|
||||
|
||||
// NewHelp creates a new help model.
|
||||
func NewHelp() HelpModel {
|
||||
return HelpModel{
|
||||
visible: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Show displays the help overlay.
|
||||
func (m *HelpModel) Show() {
|
||||
m.visible = true
|
||||
}
|
||||
|
||||
// Hide hides the help overlay.
|
||||
func (m *HelpModel) Hide() {
|
||||
m.visible = false
|
||||
}
|
||||
|
||||
// Toggle toggles the help overlay visibility.
|
||||
func (m *HelpModel) Toggle() {
|
||||
m.visible = !m.visible
|
||||
}
|
||||
|
||||
// IsVisible returns true if the help overlay is visible.
|
||||
func (m HelpModel) IsVisible() bool {
|
||||
return m.visible
|
||||
}
|
||||
|
||||
// Render renders the help overlay.
|
||||
func (m HelpModel) Render(keys KeyMap, styles Styles) string {
|
||||
if !m.visible {
|
||||
return ""
|
||||
}
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
// Title
|
||||
title := styles.HeaderBar.Render("Keybindings - Press ? or esc to close")
|
||||
b.WriteString(title)
|
||||
b.WriteString("\n\n")
|
||||
|
||||
// Define keybinding groups
|
||||
groups := []struct {
|
||||
name string
|
||||
binds []keyHelp
|
||||
}{
|
||||
{
|
||||
name: "Global",
|
||||
binds: []keyHelp{
|
||||
{keys.Help, "Show/hide this help"},
|
||||
{keys.Quit, "Quit the application"},
|
||||
{keys.RefreshNow, "Refresh data now"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Filters",
|
||||
binds: []keyHelp{
|
||||
{keys.Search, "Search by title/details"},
|
||||
{keys.Priority, "Cycle priority filter"},
|
||||
{keys.Category, "Cycle category filter"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Navigation",
|
||||
binds: []keyHelp{
|
||||
{keys.FocusNext, "Toggle focus (table/details)"},
|
||||
{keys.Sort, "Cycle sort order"},
|
||||
{keys.JumpToTop, "Jump to top (g)"},
|
||||
{keys.JumpToBottom, "Jump to bottom (G)"},
|
||||
{keys.Down, "Move down (j)"},
|
||||
{keys.Up, "Move up (k)"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Actions",
|
||||
binds: []keyHelp{
|
||||
{keys.AckToggle, "Acknowledge/unacknowledge issue"},
|
||||
{keys.Export, "Export issues to JSON"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Render each group
|
||||
for i, group := range groups {
|
||||
if i > 0 {
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
// Group header
|
||||
groupTitle := styles.HeaderKey.Render(group.name + ":")
|
||||
b.WriteString(groupTitle)
|
||||
b.WriteString("\n")
|
||||
|
||||
// Keybindings in this group
|
||||
for _, kb := range group.binds {
|
||||
line := renderKeyHelp(kb, styles)
|
||||
b.WriteString(line)
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Render collector health icon legend
|
||||
b.WriteString("\n")
|
||||
legendTitle := styles.HeaderKey.Render("Legend:")
|
||||
b.WriteString(legendTitle)
|
||||
b.WriteString("\n")
|
||||
legendText := styles.HeaderVal.Render(" Collector health: ✓ (OK), ⚠ (DEGRADED), ✗ (ERROR)")
|
||||
b.WriteString(legendText)
|
||||
b.WriteString("\n")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type keyHelp struct {
|
||||
binding key.Binding
|
||||
help string
|
||||
}
|
||||
|
||||
func renderKeyHelp(kb keyHelp, styles Styles) string {
|
||||
// Get key names from the binding
|
||||
keys := kb.binding.Keys()
|
||||
if len(keys) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Format key names
|
||||
keyStr := strings.Join(keys, ", ")
|
||||
keyStyled := styles.HeaderVal.Render(keyStr)
|
||||
|
||||
// Format help text
|
||||
helpStyled := styles.HeaderVal.Render(kb.help)
|
||||
|
||||
// Combine with padding
|
||||
padding := ""
|
||||
if needed := 10 - len(keyStr); needed > 0 {
|
||||
padding = strings.Repeat(" ", needed)
|
||||
}
|
||||
return fmt.Sprintf(" %s%s%s", keyStyled, padding, helpStyled)
|
||||
}
|
||||
Reference in New Issue
Block a user