2025-10-10 22:43:47 -04:00
package tui
import (
"fmt"
"rmm-hunter/internal/suspicious"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
// RequestEliminateMsg is emitted by the detail view when '!' is pressed
type RequestEliminateMsg struct {
TypeKey string
Index int
}
// DeletedMsg is emitted after successful elimination to update lists
type DeletedMsg struct {
TypeKey string
Index int
}
type DetailViewModel struct {
typeKey string
index int
data suspicious . Suspicious
2025-10-10 22:53:20 -04:00
// When modal* != "", show modal and require ESC to dismiss
modalErr string
modalWarn string
2025-10-10 22:43:47 -04:00
}
func NewDetailView ( typeKey string , index int , data suspicious . Suspicious ) DetailViewModel {
return DetailViewModel { typeKey : typeKey , index : index , data : data }
}
func ( m DetailViewModel ) Init ( ) tea . Cmd { return nil }
func ( m DetailViewModel ) Update ( msg tea . Msg ) ( tea . Model , tea . Cmd ) {
switch v := msg . ( type ) {
case tea . KeyMsg :
2025-10-10 22:53:20 -04:00
if m . modalErr != "" || m . modalWarn != "" {
2025-10-10 22:43:47 -04:00
// Modal active: only ESC dismisses
if v . String ( ) == "esc" {
m . modalErr = ""
2025-10-10 22:53:20 -04:00
m . modalWarn = ""
2025-10-10 22:43:47 -04:00
}
return m , nil
}
switch v . String ( ) {
case "left" :
return m , func ( ) tea . Msg { return BackMsg { } }
case "!" :
return m , func ( ) tea . Msg { return RequestEliminateMsg { TypeKey : m . typeKey , Index : m . index } }
case "q" , "esc" , "ctrl+c" :
return m , tea . Quit
}
}
return m , nil
}
func ( m DetailViewModel ) View ( ) string {
title := lipgloss . NewStyle ( ) . Bold ( true ) . Render ( "Details — press ! to eliminate, Left to go back, q to quit" )
body := m . renderDetails ( )
view := title + "\n\n" + body
2025-10-10 22:53:20 -04:00
if m . modalWarn != "" {
modal := lipgloss . NewStyle ( ) . Padding ( 1 , 2 ) . Foreground ( lipgloss . Color ( "214" ) ) . Border ( lipgloss . RoundedBorder ( ) ) . Render ( "Warning:\n" + m . modalWarn + "\n\nPress ESC to dismiss" )
view += "\n\n" + modal
}
2025-10-10 22:43:47 -04:00
if m . modalErr != "" {
modal := lipgloss . NewStyle ( ) . Padding ( 1 , 2 ) . Foreground ( lipgloss . Color ( "203" ) ) . Border ( lipgloss . RoundedBorder ( ) ) . Render ( "Elimination failed:\n" + m . modalErr + "\n\nPress ESC to dismiss" )
view += "\n\n" + modal
}
return view
}
func ( m DetailViewModel ) renderDetails ( ) string {
switch m . typeKey {
case "autoruns" :
ar := m . data . AutoRuns [ m . index ]
return fmt . Sprintf ( "Name: %s\nCommand: %s\nLocation: %s\nEnabled: %v\nDescription: %s" , ar . Name , ar . Command , ar . Location , ar . Enabled , ar . Description )
case "binaries" :
b := m . data . Binaries [ m . index ]
return fmt . Sprintf ( "Binary: %s\nAction: delete file" , b )
case "connections" :
c := m . data . OutboundConnections [ m . index ]
return fmt . Sprintf ( "Local: %s\nRemote: %s\nHost: %s\nState: %s\nPID: %s\nProcess: %s\nAction: add firewall block (placeholder)" , c . LocalAddr , c . RemoteAddr , c . RemoteHost , c . State , c . PID , c . Process )
case "directories" :
d := m . data . Directories [ m . index ]
return fmt . Sprintf ( "Directory: %s\nAction: delete recursively" , d )
case "processes" :
p := m . data . Processes [ m . index ]
return fmt . Sprintf ( "Name: %s\nPID: %d\nPPID: %d\nParent: %s\nArgs: %s\nCreated: %s\nPath: %s\nAction: stop then delete (placeholder)" , p . Name , p . PID , p . PPID , p . Parent , p . Args , p . Created , p . Path )
case "scheduledTasks" :
t := m . data . ScheduledTasks [ m . index ]
return fmt . Sprintf ( "Name: %s\nAuthor: %s\nState: %s\nEnabled: %v\nLastResult: %s\nNextRun: %s\nLastRun: %s\nPath: %s\nAction: disable then delete (placeholder)" , t . Name , t . Author , t . State , t . Enabled , t . LastResult , t . NextRun , t . LastRun , t . Path )
case "services" :
s := m . data . Services [ m . index ]
return fmt . Sprintf ( "Name: %s\nDisplay: %s\nType: %s\nStartType: %s\nBinPath: %s\nStartName: %s\nDescription: %s\nAction: stop then delete (placeholder)" , s . Name , s . DisplayName , s . ServiceType , s . StartType , s . BinaryPathName , s . ServiceStartName , s . Description )
}
return ""
}