package tui import ( "fmt" "strings" ) // View is PURE: Model → string. No side effects. func View(model Model) string { switch model.Screen { case ScreenMain: return viewMain(model) case ScreenAgentList: return viewAgentList(model) case ScreenAgentActions: return viewAgentActions(model) case ScreenLogs: return viewLogs(model) case ScreenServer: return viewServer(model) case ScreenTests: return viewTests(model) case ScreenTestOutput: return viewTestOutput(model) default: return "" } } func viewMain(m Model) string { var b strings.Builder b.WriteString("\n Bot Server Dashboard\n") b.WriteString(" " + strings.Repeat("─", 36) + "\n") // Summary running, stopped, disabled := countStatuses(m.Agents) total := len(m.Agents) if total > 0 { b.WriteString(fmt.Sprintf(" %d agents (%d running, %d stopped, %d disabled)\n\n", total, running, stopped, disabled)) } else { b.WriteString(" Loading...\n\n") } // Menu for i, opt := range MainMenuOptions() { cursor := " " if i == m.Cursor { cursor = "> " } b.WriteString(fmt.Sprintf(" %s%-16s %s\n", cursor, opt.Label, opt.Desc)) } b.WriteString("\n ↑↓ navegar enter seleccionar q salir\n") return b.String() } func viewAgentList(m Model) string { var b strings.Builder b.WriteString("\n Agents\n") b.WriteString(" " + strings.Repeat("─", 60) + "\n") if len(m.Agents) == 0 { b.WriteString(" No agents found.\n") } for i, a := range m.Agents { cursor := " " if i == m.Cursor { cursor = "> " } icon := "○" status := "stopped" if !a.Enabled { icon = " " status = "disabled" } else if a.Running { icon = "●" if a.Instances > 1 { status = fmt.Sprintf("running %d instances", a.Instances) } else { status = fmt.Sprintf("running PID %d", a.PID) } } b.WriteString(fmt.Sprintf(" %s%s %-20s %-8s %s\n", cursor, icon, a.ID, a.Version, status)) } if m.StatusMsg != "" { b.WriteString("\n " + m.StatusMsg + "\n") } b.WriteString("\n ↑↓ navegar enter acciones 0 volver\n") return b.String() } func viewAgentActions(m Model) string { var b strings.Builder if m.Selected == nil { return " No agent selected.\n" } a := m.Selected var icon string switch { case !a.Enabled: icon = " disabled" case a.Running: icon = "● enabled (running)" default: icon = "○ enabled (stopped)" } b.WriteString(fmt.Sprintf("\n %s %s\n", a.ID, icon)) b.WriteString(" " + strings.Repeat("─", 44) + "\n") if a.Desc != "" { b.WriteString(" " + a.Desc + "\n") } b.WriteString("\n") opts := AgentActionOptions(a.Enabled) for i, opt := range opts { cursor := " " if i == m.Cursor { cursor = "> " } b.WriteString(fmt.Sprintf(" %s%-16s %s\n", cursor, opt.Label, opt.Desc)) } if m.StatusMsg != "" { b.WriteString("\n " + m.StatusMsg + "\n") } b.WriteString("\n ↑↓ navegar enter ejecutar 0 volver\n") return b.String() } func viewLogs(m Model) string { var b strings.Builder agentID := "Launcher" if m.Selected != nil { agentID = m.Selected.ID } b.WriteString(fmt.Sprintf("\n %s — Logs\n", agentID)) b.WriteString(" " + strings.Repeat("─", 60) + "\n") if len(m.LogLines) == 0 { b.WriteString(" (no log data)\n") } else { visible := visibleLogLines(m) end := m.LogScroll + visible if end > len(m.LogLines) { end = len(m.LogLines) } start := m.LogScroll if start >= len(m.LogLines) { start = max(0, len(m.LogLines)-1) } for _, line := range m.LogLines[start:end] { // Truncate long lines if len(line) > m.WindowWidth-4 && m.WindowWidth > 10 { line = line[:m.WindowWidth-7] + "..." } b.WriteString(" " + line + "\n") } } b.WriteString("\n ↑↓ scroll r recargar 0 volver\n") return b.String() } func viewServer(m Model) string { var b strings.Builder b.WriteString("\n Launcher Management\n") b.WriteString(" " + strings.Repeat("─", 44) + "\n") // Launcher status if m.LauncherRunning { b.WriteString(fmt.Sprintf(" ● Launcher running PID %d\n", m.LauncherPID)) parts := []string{} if m.LauncherUptime != "" { parts = append(parts, "uptime: "+m.LauncherUptime) } if m.LauncherMemory != "" { parts = append(parts, "mem: "+m.LauncherMemory) } if m.LauncherCPU != "" { parts = append(parts, "cpu: "+m.LauncherCPU) } if m.LauncherLogSize != "" { parts = append(parts, "log: "+m.LauncherLogSize) } if len(parts) > 0 { b.WriteString(" " + strings.Join(parts, " ") + "\n") } } else { b.WriteString(" ○ Launcher stopped\n") } // Agent summary _, _, disabled := countStatuses(m.Agents) enabled := len(m.Agents) - disabled if len(m.Agents) > 0 { b.WriteString(fmt.Sprintf("\n %d agents (%d enabled, %d disabled)\n", len(m.Agents), enabled, disabled)) for _, a := range m.Agents { icon := "●" if !a.Enabled { icon = "○" } b.WriteString(fmt.Sprintf(" %s %s\n", icon, a.ID)) } } b.WriteString("\n") // Action menu for i, opt := range ServerMenuOptions(m.LauncherRunning) { cursor := " " if i == m.Cursor { cursor = "> " } b.WriteString(fmt.Sprintf(" %s%-20s %s\n", cursor, opt.Label, opt.Desc)) } if m.StatusMsg != "" { b.WriteString("\n " + m.StatusMsg + "\n") } b.WriteString("\n ↑↓ navegar enter ejecutar 0 volver\n") return b.String() } func viewTests(m Model) string { var b strings.Builder b.WriteString("\n Tests\n") b.WriteString(" " + strings.Repeat("─", 44) + "\n") for i, opt := range TestMenuOptions() { cursor := " " if i == m.Cursor { cursor = "> " } b.WriteString(fmt.Sprintf(" %s%-22s %s\n", cursor, opt.Label, opt.Desc)) } if m.LastTestKind != TestKindNone { b.WriteString(fmt.Sprintf("\n Last run: %s", testKindLabel(m.LastTestKind))) if m.StatusMsg != "" && (strings.HasSuffix(m.StatusMsg, "PASSED") || strings.HasSuffix(m.StatusMsg, "FAILED")) { // Extract result from status if strings.HasSuffix(m.StatusMsg, "PASSED") { b.WriteString(" — PASSED") } else { b.WriteString(" — FAILED") } } b.WriteString("\n") } b.WriteString("\n ↑↓ navegar enter ejecutar 0 volver\n") return b.String() } func viewTestOutput(m Model) string { var b strings.Builder title := "Test Results" if m.LastTestKind != TestKindNone { title = "Test Results — " + testKindLabel(m.LastTestKind) } b.WriteString("\n " + title + "\n") b.WriteString(" " + strings.Repeat("─", 60) + "\n") if m.StatusMsg != "" { b.WriteString(" " + m.StatusMsg + "\n\n") } if len(m.LogLines) == 0 { b.WriteString(" Running tests...\n") } else { visible := visibleLogLines(m) end := m.LogScroll + visible if end > len(m.LogLines) { end = len(m.LogLines) } start := m.LogScroll if start >= len(m.LogLines) { start = max(0, len(m.LogLines)-1) } for _, line := range m.LogLines[start:end] { if len(line) > m.WindowWidth-4 && m.WindowWidth > 10 { line = line[:m.WindowWidth-7] + "..." } b.WriteString(" " + line + "\n") } } b.WriteString("\n ↑↓ scroll r re-ejecutar 0 volver\n") return b.String() } func countStatuses(agents []AgentView) (running, stopped, disabled int) { for _, a := range agents { switch { case !a.Enabled: disabled++ case a.Running: running++ default: stopped++ } } return }