sshkeeper/internal/tui/tunnel.go

116 lines
2.4 KiB
Go

package tui
import (
"fmt"
"strings"
"time"
"github.com/charmbracelet/bubbles/list"
"github.com/charmbracelet/bubbletea"
"github.com/mirivlad/sshkeeper/internal/model"
"github.com/mirivlad/sshkeeper/internal/tunnel"
)
// --- Tunnel manager screen ---
type tunnelScreenModel struct {
list list.Model
tunnels []*model.TunnelState
width int
height int
err error
}
type tunnelItem struct {
state *model.TunnelState
}
func (i tunnelItem) Title() string {
status := "stopped"
if tunnel.IsRunning(i.state.ID) {
status = "running"
}
duration := time.Since(i.state.StartedAt).Round(time.Second)
return fmt.Sprintf("%-30s PID %-8d %-8s %s",
truncate(i.state.Name, 30),
i.state.PID,
status,
duration,
)
}
func (i tunnelItem) Description() string {
preview := fmt.Sprintf("ssh -N ... → %s", i.state.ServerAlias)
if i.state.LastError != "" {
return fmt.Sprintf(" %s\n ✗ %s", preview, i.state.LastError)
}
return " " + preview
}
func (i tunnelItem) FilterValue() string {
return i.state.Name + " " + i.state.ServerAlias
}
func newTunnelScreenModel(w, h int) *tunnelScreenModel {
l := list.New([]list.Item{}, list.NewDefaultDelegate(), w, h-6)
l.Title = "Tunnel Manager"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.Styles.Title = titleStyle
return &tunnelScreenModel{
list: l,
width: w,
height: h,
}
}
func (m *tunnelScreenModel) loadTunnels() tea.Cmd {
return func() tea.Msg {
states := tunnel.List()
items := make([]list.Item, len(states))
for i, s := range states {
items[i] = tunnelItem{state: s}
}
return tunnelsLoadedMsg{items: items}
}
}
func (m *tunnelScreenModel) rebuildList() {
items := make([]list.Item, len(m.tunnels))
for i, s := range m.tunnels {
items[i] = tunnelItem{state: s}
}
m.list.SetItems(items)
}
func (m *tunnelScreenModel) stopSelected() tea.Cmd {
if item, ok := m.list.SelectedItem().(tunnelItem); ok {
return func() tea.Msg {
return tunnelStoppedMsg{id: item.state.ID, err: tunnel.Stop(item.state.ID)}
}
}
return nil
}
func (m *tunnelScreenModel) View() string {
var b strings.Builder
b.WriteString(m.list.View())
b.WriteString("\n\n")
b.WriteString(renderHelp([]helpItem{
{Key: "Ctrl+D (s)", Action: "stop tunnel"},
{Key: "Ctrl+R (r)", Action: "refresh"},
{Key: "Esc", Action: "back"},
}, m.width))
return b.String()
}
type tunnelsLoadedMsg struct {
items []list.Item
}
type tunnelStoppedMsg struct {
id int64
err error
}