Add TUI hardware banner and polish SAT summaries

This commit is contained in:
Mikhail Chusavitin
2026-03-15 14:27:01 +03:00
parent b483e2ce35
commit b8c235b5ac
12 changed files with 667 additions and 47 deletions

View File

@@ -23,3 +23,7 @@ type exportTargetsMsg struct {
targets []platform.RemovableTarget
err error
}
type bannerMsg struct {
text string
}

View File

@@ -179,6 +179,24 @@ func TestMainMenuAsyncActionsSetBusy(t *testing.T) {
}
}
func TestMainViewIncludesBanner(t *testing.T) {
t.Parallel()
m := newTestModel()
m.banner = "System: Test Server | S/N ABC123\nIP: 10.0.0.10"
view := m.View()
if !strings.Contains(view, "System: Test Server | S/N ABC123") {
t.Fatalf("view missing system banner:\n%s", view)
}
if !strings.Contains(view, "IP: 10.0.0.10") {
t.Fatalf("view missing ip banner:\n%s", view)
}
if !strings.Contains(view, "Select action") {
t.Fatalf("view missing menu subtitle:\n%s", view)
}
}
func TestEscapeNavigation(t *testing.T) {
t.Parallel()

View File

@@ -4,6 +4,7 @@ import (
"bee/audit/internal/app"
"bee/audit/internal/platform"
"bee/audit/internal/runtimeenv"
"strings"
tea "github.com/charmbracelet/bubbletea"
)
@@ -26,12 +27,12 @@ const (
type actionKind string
const (
actionNone actionKind = ""
actionDHCPOne actionKind = "dhcp_one"
actionStaticIPv4 actionKind = "static_ipv4"
actionExportAudit actionKind = "export_audit"
actionRunNvidiaSAT actionKind = "run_nvidia_sat"
actionRunMemorySAT actionKind = "run_memory_sat"
actionNone actionKind = ""
actionDHCPOne actionKind = "dhcp_one"
actionStaticIPv4 actionKind = "static_ipv4"
actionExportAudit actionKind = "export_audit"
actionRunNvidiaSAT actionKind = "run_nvidia_sat"
actionRunMemorySAT actionKind = "run_memory_sat"
actionRunStorageSAT actionKind = "run_storage_sat"
)
@@ -46,6 +47,7 @@ type model struct {
busyTitle string
title string
body string
banner string
mainMenu []string
networkMenu []string
serviceMenu []string
@@ -111,5 +113,7 @@ func newModel(application *app.App, runtimeMode runtimeenv.Mode) model {
}
func (m model) Init() tea.Cmd {
return nil
return func() tea.Msg {
return bannerMsg{text: strings.TrimSpace(m.app.MainBanner())}
}
}

View File

@@ -84,6 +84,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.screen = screenExportTargets
m.cursor = 0
return m, nil
case bannerMsg:
m.banner = strings.TrimSpace(msg.text)
return m, nil
}
return m, nil

View File

@@ -19,7 +19,7 @@ func (m model) View() string {
}
switch m.screen {
case screenMain:
return renderMenu("bee", "Select action", m.mainMenu, m.cursor)
return renderMainMenu("bee", m.banner, "Select action", m.mainMenu, m.cursor)
case screenNetwork:
return renderMenu("Network", "Select action", m.networkMenu, m.cursor)
case screenServices:
@@ -109,6 +109,30 @@ func renderMenu(title, subtitle string, items []string, cursor int) string {
return body.String()
}
func renderMainMenu(title, banner, subtitle string, items []string, cursor int) string {
var body strings.Builder
fmt.Fprintf(&body, "%s\n\n", title)
if banner != "" {
body.WriteString(strings.TrimSpace(banner))
body.WriteString("\n\n")
}
body.WriteString(subtitle)
body.WriteString("\n\n")
if len(items) == 0 {
body.WriteString("(no items)\n")
} else {
for i, item := range items {
prefix := " "
if i == cursor {
prefix = "> "
}
fmt.Fprintf(&body, "%s%s\n", prefix, item)
}
}
body.WriteString("\n[↑/↓] move [enter] select [esc] back [ctrl+c] quit\n")
return body.String()
}
func renderForm(title string, fields []formField, idx int) string {
var body strings.Builder
fmt.Fprintf(&body, "%s\n\n", title)