Files
porthole/internal/ui/details.go
OpenCode Test 1421b4659e 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.
2025-12-24 13:29:51 -08:00

106 lines
2.5 KiB
Go

package ui
import (
"fmt"
"sort"
"strings"
"time"
"tower/internal/model"
)
// getRollupSamples extracts sample IDs from a rollup issue's evidence.
func getRollupSamples(iss model.Issue) []string {
samplesStr := iss.Evidence["samples"]
if samplesStr == "" {
return nil
}
parts := strings.Split(samplesStr, " | ")
result := make([]string, 0, len(parts))
for _, p := range parts {
p = strings.TrimSpace(p)
if p != "" {
result = append(result, p)
}
}
return result
}
// isRollupIssue checks if an issue is a rollup issue.
func isRollupIssue(iss model.Issue) bool {
if strings.HasPrefix(iss.ID, "k8s:rollup:") {
return true
}
if iss.Category == model.CategoryKubernetes && strings.Contains(strings.ToLower(iss.Title), "rollup") {
return true
}
return false
}
func renderIssueDetails(now time.Time, mode AgeMode, iss model.Issue) string {
var b strings.Builder
fmt.Fprintf(&b, "Title: %s\n", oneLine(iss.Title))
fmt.Fprintf(&b, "Priority: %s Category: %s State: %s\n", iss.Priority, iss.Category, iss.State)
fmt.Fprintf(&b, "FirstSeen: %s\n", fmtTime(iss.FirstSeen))
fmt.Fprintf(&b, "LastSeen: %s\n", fmtTime(iss.LastSeen))
fmt.Fprintf(&b, "Age: %s\n", formatAgeWithMode(iss.Age(now), mode))
if strings.TrimSpace(iss.Details) != "" {
b.WriteString("\nDetails\n")
b.WriteString(indentBlock(strings.TrimSpace(iss.Details), " "))
b.WriteString("\n")
}
// Show affected issues for rollup issues
if isRollupIssue(iss) {
samples := getRollupSamples(iss)
if len(samples) > 0 {
b.WriteString("\nAffected Issues\n")
// Show up to 10 samples
maxSamples := 10
if len(samples) > maxSamples {
samples = samples[:maxSamples]
}
for _, sample := range samples {
fmt.Fprintf(&b, " • %s\n", sample)
}
}
}
if len(iss.Evidence) > 0 {
b.WriteString("\nEvidence\n")
keys := make([]string, 0, len(iss.Evidence))
for k := range iss.Evidence {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(&b, " %s: %s\n", k, iss.Evidence[k])
}
}
if strings.TrimSpace(iss.SuggestedFix) != "" {
b.WriteString("\nSuggested Fix\n")
b.WriteString(indentBlock(strings.TrimSpace(iss.SuggestedFix), " "))
b.WriteString("\n")
}
return strings.TrimRight(b.String(), "\n")
}
func fmtTime(t time.Time) string {
if t.IsZero() {
return "-"
}
return t.Local().Format("2006-01-02 15:04:05")
}
func indentBlock(s, prefix string) string {
lines := strings.Split(s, "\n")
for i := range lines {
lines[i] = prefix + lines[i]
}
return strings.Join(lines, "\n")
}