package tui import ( "context" "time" "bee/audit/internal/platform" tea "github.com/charmbracelet/bubbletea" ) func (m model) updateStaticForm(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "esc": m.screen = screenNetwork m.formFields = nil m.formIndex = 0 return m, nil case "up", "shift+tab": if m.formIndex > 0 { m.formIndex-- } case "down", "tab": if m.formIndex < len(m.formFields)-1 { m.formIndex++ } case "enter": if m.formIndex < len(m.formFields)-1 { m.formIndex++ return m, nil } cfg := m.app.ParseStaticIPv4Config(m.selectedIface, []string{ m.formFields[0].Value, m.formFields[1].Value, m.formFields[2].Value, m.formFields[3].Value, }) m.busy = true m.busyTitle = "Static IPv4: " + m.selectedIface return m, func() tea.Msg { result, err := m.app.SetStaticIPv4Result(cfg) return resultMsg{title: result.Title, body: result.Body, err: err, back: screenNetwork} } case "backspace": field := &m.formFields[m.formIndex] if len(field.Value) > 0 { field.Value = field.Value[:len(field.Value)-1] } default: if msg.Type == tea.KeyRunes && len(msg.Runes) > 0 { m.formFields[m.formIndex].Value += string(msg.Runes) } } return m, nil } func (m model) updateConfirm(msg tea.KeyMsg) (tea.Model, tea.Cmd) { switch msg.String() { case "left", "up", "tab": if m.cursor > 0 { m.cursor-- } case "right", "down": if m.cursor < 1 { m.cursor++ } case "esc": m.screen = m.confirmCancelTarget() m.cursor = 0 m.pendingAction = actionNone return m, nil case "enter": if m.cursor == 1 { // Cancel m.screen = m.confirmCancelTarget() m.cursor = 0 m.pendingAction = actionNone return m, nil } m.busy = true switch m.pendingAction { case actionExportBundle: m.busyTitle = "Export support bundle" target := *m.selectedTarget return m, func() tea.Msg { result, err := m.app.ExportSupportBundleResult(target) return resultMsg{title: result.Title, body: result.Body, err: err, back: screenMain} } case actionRunAll: return m.executeRunAll() case actionRunMemorySAT: m.busyTitle = "Memory test" m.progressPrefix = "memory" m.progressSince = time.Now() m.progressLines = nil since := m.progressSince return m, tea.Batch( func() tea.Msg { result, err := m.app.RunMemoryAcceptancePackResult("") return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck} }, pollSATProgress("memory", since), ) case actionRunStorageSAT: m.busyTitle = "Storage test" m.progressPrefix = "storage" m.progressSince = time.Now() m.progressLines = nil since := m.progressSince return m, tea.Batch( func() tea.Msg { result, err := m.app.RunStorageAcceptancePackResult("") return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck} }, pollSATProgress("storage", since), ) case actionRunCPUSAT: m.busyTitle = "CPU test" m.progressPrefix = "cpu" m.progressSince = time.Now() m.progressLines = nil since := m.progressSince durationSec := hcCPUDurations[m.hcMode] return m, tea.Batch( func() tea.Msg { result, err := m.app.RunCPUAcceptancePackResult("", durationSec) return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck} }, pollSATProgress("cpu", since), ) case actionRunAMDGPUSAT: m.busyTitle = "AMD GPU test" m.progressPrefix = "gpu-amd" m.progressSince = time.Now() m.progressLines = nil since := m.progressSince return m, tea.Batch( func() tea.Msg { result, err := m.app.RunAMDAcceptancePackResult("") return resultMsg{title: result.Title, body: result.Body, err: err, back: screenHealthCheck} }, pollSATProgress("gpu-amd", since), ) case actionRunFanStress: return m.startGPUStressTest() case actionInstallToDisk: return m.startInstall() case actionRunNCCLTests: m.busy = true m.busyTitle = "NCCL bandwidth test" ctx, cancel := context.WithCancel(context.Background()) m.ncclCancel = cancel return m, func() tea.Msg { result, err := m.app.RunNCCLTestsResult(ctx) return resultMsg{title: result.Title, body: result.Body, err: err, back: screenBurnInTests} } } case "ctrl+c": return m, tea.Quit } return m, nil } func (m model) confirmCancelTarget() screen { switch m.pendingAction { case actionExportBundle: return screenExportTargets case actionRunAll, actionRunMemorySAT, actionRunStorageSAT, actionRunCPUSAT, actionRunAMDGPUSAT: return screenHealthCheck case actionRunFanStress, actionRunNCCLTests: return screenBurnInTests case actionInstallToDisk: return screenInstallDiskPick default: return screenMain } } // hcFanStressOpts builds FanStressOptions for the selected mode, auto-detecting all GPUs. func hcFanStressOpts(hcMode int, application interface { ListNvidiaGPUs() ([]platform.NvidiaGPU, error) }) platform.FanStressOptions { // Phase durations per mode: [baseline, load1, pause, load2] type durations struct{ baseline, load1, pause, load2 int } modes := [3]durations{ {30, 120, 30, 120}, // Quick: ~5 min total {60, 300, 60, 300}, // Standard: ~12 min total {60, 600, 120, 600}, // Express: ~24 min total } if hcMode < 0 || hcMode >= len(modes) { hcMode = 0 } d := modes[hcMode] // Use all detected NVIDIA GPUs. var indices []int if gpus, err := application.ListNvidiaGPUs(); err == nil { for _, g := range gpus { indices = append(indices, g.Index) } } // Use nearly full GPU memory on the smallest GPU (leave 512 MB for driver overhead). sizeMB := 64 if gpus, err := application.ListNvidiaGPUs(); err == nil { for _, g := range gpus { free := g.MemoryMB - 512 if free > 0 && (sizeMB == 64 || free < sizeMB) { sizeMB = free } } } return platform.FanStressOptions{ BaselineSec: d.baseline, Phase1DurSec: d.load1, PauseSec: d.pause, Phase2DurSec: d.load2, SizeMB: sizeMB, GPUIndices: indices, } }