mirror of
https://github.com/hanzoai/insights.git
synced 2026-10-30 16:15:31 +00:00
Modules declare go 1.27.1 and the build images follow. go fix applied the modernizers, and the ptr helper went once new(expr) could say it; omitzero is not applied, since it changes what the JSON says. Co-authored-by: Hanzo Dev <[email protected]>
175 lines
4.5 KiB
Go
175 lines
4.5 KiB
Go
package process
|
|
|
|
import (
|
|
"sync"
|
|
|
|
tea "charm.land/bubbletea/v2"
|
|
"github.com/insights/insights/phrocs/internal/config"
|
|
"github.com/insights/insights/phrocs/internal/docker"
|
|
)
|
|
|
|
// Orchestrates all processes for the dev environment
|
|
type Manager struct {
|
|
mu sync.Mutex
|
|
procs []*Process
|
|
byName map[string]*Process
|
|
send func(tea.Msg)
|
|
scrollback int
|
|
globalShell string
|
|
quitCh chan struct{}
|
|
quitOnce sync.Once
|
|
}
|
|
|
|
func NewManager(cfg *config.Config) *Manager {
|
|
mgr := &Manager{
|
|
scrollback: cfg.Scrollback,
|
|
globalShell: cfg.Shell,
|
|
quitCh: make(chan struct{}),
|
|
}
|
|
|
|
names := cfg.OrderedNames()
|
|
mgr.procs = make([]*Process, 0, len(names))
|
|
mgr.byName = make(map[string]*Process, len(names))
|
|
|
|
for _, name := range names {
|
|
pcfg := cfg.Procs[name]
|
|
// Strip trailing "docker compose ... logs" from docker-compose shells
|
|
if docker.IsDockerComposeShell(pcfg.Shell) {
|
|
pcfg.Shell = docker.StripComposeLogsTail(pcfg.Shell)
|
|
}
|
|
proc := NewProcess(name, pcfg, cfg.Scrollback, cfg.Shell)
|
|
mgr.procs = append(mgr.procs, proc)
|
|
mgr.byName[name] = proc
|
|
}
|
|
|
|
return mgr
|
|
}
|
|
|
|
// UpdateDefaults updates the manager's default scrollback and global shell
|
|
// to match a new config, so that subsequent AddShell calls use the new values.
|
|
func (m *Manager) UpdateDefaults(cfg *config.Config) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.scrollback = cfg.Scrollback
|
|
m.globalShell = cfg.Shell
|
|
}
|
|
|
|
// SetSend must be called before StartAll
|
|
func (m *Manager) SetSend(send func(tea.Msg)) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
m.send = send
|
|
}
|
|
|
|
// Launches all processes whose autostart is not explicitly false
|
|
func (m *Manager) StartAll() {
|
|
m.mu.Lock()
|
|
send := m.send
|
|
procs := m.procs
|
|
m.mu.Unlock()
|
|
|
|
for _, p := range procs {
|
|
if p.Cfg.ShouldAutostart() {
|
|
_ = p.Start(send)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Stops every running process in parallel and waits for all of them to exit
|
|
// (with SIGKILL escalation). Called on quit to ensure no orphaned processes
|
|
// keep ports occupied.
|
|
func (m *Manager) StopAll() {
|
|
m.mu.Lock()
|
|
procs := m.procs
|
|
m.mu.Unlock()
|
|
var wg sync.WaitGroup
|
|
for _, p := range procs {
|
|
wg.Go(func() {
|
|
p.Stop()
|
|
})
|
|
}
|
|
wg.Wait()
|
|
}
|
|
|
|
// Returns the ordered slice of all processes (safe for reading status/lines)
|
|
func (m *Manager) Procs() []*Process {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
cp := make([]*Process, len(m.procs))
|
|
copy(cp, m.procs)
|
|
return cp
|
|
}
|
|
|
|
// Returns a process by name
|
|
func (m *Manager) Get(name string) (*Process, bool) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
p, ok := m.byName[name]
|
|
return p, ok
|
|
}
|
|
|
|
// Returns the send function so the TUI can pass it to Restart calls
|
|
func (m *Manager) Send() func(tea.Msg) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.send
|
|
}
|
|
|
|
// Add creates a new process from config and appends it to the manager.
|
|
// If a process with the same name already exists, it is a no-op.
|
|
func (m *Manager) Add(name string, pcfg config.ProcConfig, scrollback int, globalShell string) *Process {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if _, ok := m.byName[name]; ok {
|
|
return m.byName[name]
|
|
}
|
|
if docker.IsDockerComposeShell(pcfg.Shell) {
|
|
pcfg.Shell = docker.StripComposeLogsTail(pcfg.Shell)
|
|
}
|
|
proc := NewProcess(name, pcfg, scrollback, globalShell)
|
|
m.procs = append(m.procs, proc)
|
|
m.byName[name] = proc
|
|
return proc
|
|
}
|
|
|
|
// AddShell creates a new process from a shell command using the manager's
|
|
// default scrollback and global shell. If a process with the same name already
|
|
// exists, it is a no-op and the existing process is returned.
|
|
func (m *Manager) AddShell(name, shell string) *Process {
|
|
return m.Add(name, config.ProcConfig{Shell: shell}, m.scrollback, m.globalShell)
|
|
}
|
|
|
|
// QuitCh returns a channel that is closed when Quit is invoked.
|
|
// Intended for the detached main loop to block on, then tear down cleanly.
|
|
func (m *Manager) QuitCh() <-chan struct{} {
|
|
return m.quitCh
|
|
}
|
|
|
|
// Quit signals the manager to shut down. Idempotent; safe to call multiple times.
|
|
// Does not stop processes itself — the caller is responsible for calling StopAll
|
|
// after observing the quit signal, so teardown order stays in one place.
|
|
func (m *Manager) Quit() {
|
|
m.quitOnce.Do(func() { close(m.quitCh) })
|
|
}
|
|
|
|
// Remove stops a process and removes it from the manager.
|
|
// Returns true if the process was found and removed.
|
|
func (m *Manager) Remove(name string) bool {
|
|
m.mu.Lock()
|
|
p, ok := m.byName[name]
|
|
if !ok {
|
|
m.mu.Unlock()
|
|
return false
|
|
}
|
|
delete(m.byName, name)
|
|
for i, proc := range m.procs {
|
|
if proc.Name == name {
|
|
m.procs = append(m.procs[:i], m.procs[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
m.mu.Unlock()
|
|
p.Stop()
|
|
return true
|
|
}
|