170 lines
4.9 KiB
Go
170 lines
4.9 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"bee/audit/internal/platform"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
)
|
|
|
|
func (m model) View() string {
|
|
if m.busy {
|
|
title := "bee"
|
|
if m.busyTitle != "" {
|
|
title = m.busyTitle
|
|
}
|
|
return fmt.Sprintf("%s\n\nWorking...\n\n[ctrl+c] quit\n", title)
|
|
}
|
|
switch m.screen {
|
|
case screenMain:
|
|
return renderMainMenu("bee", m.banner, "Select action", m.mainMenu, m.cursor)
|
|
case screenNetwork:
|
|
return renderMenu("Network", "Select action", m.networkMenu, m.cursor)
|
|
case screenServices:
|
|
return renderMenu("Services", "Select service", m.services, m.cursor)
|
|
case screenServiceAction:
|
|
items := make([]string, len(m.serviceMenu))
|
|
copy(items, m.serviceMenu)
|
|
return renderMenu("Service: "+m.selectedService, "Select action", items, m.cursor)
|
|
case screenAcceptance:
|
|
return renderMenu("Acceptance tests", "Select action", []string{"Run NVIDIA command pack", "Run memory test", "Run storage diagnostic pack", "Back"}, m.cursor)
|
|
case screenExportTargets:
|
|
return renderMenu("Export audit", "Select removable filesystem", renderTargetItems(m.targets), m.cursor)
|
|
case screenInterfacePick:
|
|
return renderMenu("Interfaces", "Select interface", renderInterfaceItems(m.interfaces), m.cursor)
|
|
case screenStaticForm:
|
|
return renderForm("Static IPv4: "+m.selectedIface, m.formFields, m.formIndex)
|
|
case screenConfirm:
|
|
title, body := m.confirmBody()
|
|
return renderConfirm(title, body, m.cursor)
|
|
case screenOutput:
|
|
return fmt.Sprintf("%s\n\n%s\n\n[enter/esc] back [ctrl+c] quit\n", m.title, strings.TrimSpace(m.body))
|
|
default:
|
|
return "bee\n"
|
|
}
|
|
}
|
|
|
|
func (m model) confirmBody() (string, string) {
|
|
switch m.pendingAction {
|
|
case actionExportAudit:
|
|
if m.selectedTarget == nil {
|
|
return "Export audit", "No target selected"
|
|
}
|
|
return "Export audit", fmt.Sprintf("Copy latest audit JSON to %s?", m.selectedTarget.Device)
|
|
case actionRunNvidiaSAT:
|
|
return "NVIDIA SAT", "Run NVIDIA acceptance command pack?"
|
|
case actionRunMemorySAT:
|
|
return "Memory SAT", "Run runtime memory test with memtester?"
|
|
case actionRunStorageSAT:
|
|
return "Storage SAT", "Run storage diagnostic pack and start short self-tests where supported?"
|
|
default:
|
|
return "Confirm", "Proceed?"
|
|
}
|
|
}
|
|
|
|
func renderTargetItems(targets []platform.RemovableTarget) []string {
|
|
items := make([]string, 0, len(targets))
|
|
for _, target := range targets {
|
|
desc := fmt.Sprintf("%s [%s %s]", target.Device, target.FSType, target.Size)
|
|
if target.Label != "" {
|
|
desc += " label=" + target.Label
|
|
}
|
|
if target.Mountpoint != "" {
|
|
desc += " mounted=" + target.Mountpoint
|
|
}
|
|
items = append(items, desc)
|
|
}
|
|
return items
|
|
}
|
|
|
|
func renderInterfaceItems(interfaces []platform.InterfaceInfo) []string {
|
|
items := make([]string, 0, len(interfaces))
|
|
for _, iface := range interfaces {
|
|
label := iface.Name
|
|
if len(iface.IPv4) > 0 {
|
|
label += " [" + strings.Join(iface.IPv4, ", ") + "]"
|
|
}
|
|
items = append(items, label)
|
|
}
|
|
return items
|
|
}
|
|
|
|
func renderMenu(title, subtitle string, items []string, cursor int) string {
|
|
var body strings.Builder
|
|
fmt.Fprintf(&body, "%s\n\n%s\n\n", title, subtitle)
|
|
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 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)
|
|
for i, field := range fields {
|
|
prefix := " "
|
|
if i == idx {
|
|
prefix = "> "
|
|
}
|
|
fmt.Fprintf(&body, "%s%s: %s\n", prefix, field.Label, field.Value)
|
|
}
|
|
body.WriteString("\n[tab/↑/↓] move [enter] next/submit [backspace] delete [esc] cancel\n")
|
|
return body.String()
|
|
}
|
|
|
|
func renderConfirm(title, body string, cursor int) string {
|
|
options := []string{"Confirm", "Cancel"}
|
|
var out strings.Builder
|
|
fmt.Fprintf(&out, "%s\n\n%s\n\n", title, body)
|
|
for i, option := range options {
|
|
prefix := " "
|
|
if i == cursor {
|
|
prefix = "> "
|
|
}
|
|
fmt.Fprintf(&out, "%s%s\n", prefix, option)
|
|
}
|
|
out.WriteString("\n[←/→/↑/↓] move [enter] select [esc] cancel\n")
|
|
return out.String()
|
|
}
|
|
|
|
func resultCmd(title, body string, err error, back screen) tea.Cmd {
|
|
return func() tea.Msg {
|
|
return resultMsg{title: title, body: body, err: err, back: back}
|
|
}
|
|
}
|