1af0457c1f
Actualiza el dashboard TUI y el process manager para el modelo de launcher unificado donde todos los agentes corren en un solo proceso. Dashboard (pkg/tui): - model.go: campos de estado del launcher (PID, uptime, memory, CPU, log size) - model.go: ServerMenuOptions(running) contextual, AgentActionOptions(enabled) - messages.go: MsgAgentsLoaded incluye estado del launcher, MsgServerActionDone/MsgRebuildDone simplificados - update.go: intents nuevos (Enable/Disable agent, Start/Stop/Restart/Kill launcher) - view.go: vista de servidor muestra stats del launcher, agentes muestran enabled/disabled Shell adapter (shell/tui): - adapter.go: reescrito para usar métodos unificados (StartUnified, StopUnified, ToggleEnabled, StatusAllUnified, UnifiedStats, UnifiedLogTail) Process manager (shell/process): - manager.go: métodos StartUnified, StopUnified, KillUnified, IsUnifiedRunning, UnifiedPID, UnifiedStats, UnifiedLogTail, StatusAllUnified, ToggleEnabled Los agentes ya no se inician/detienen individualmente desde el dashboard. Se habilitan/deshabilitan en config y se reinicia el launcher para aplicar. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
252 lines
5.4 KiB
Go
252 lines
5.4 KiB
Go
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)
|
|
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 countStatuses(agents []AgentView) (running, stopped, disabled int) {
|
|
for _, a := range agents {
|
|
switch {
|
|
case !a.Enabled:
|
|
disabled++
|
|
case a.Running:
|
|
running++
|
|
default:
|
|
stopped++
|
|
}
|
|
}
|
|
return
|
|
}
|