sshkeeper/internal/tui/session.go

135 lines
3.4 KiB
Go

package tui
import (
"fmt"
"time"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
sessionpkg "github.com/mirivlad/sshkeeper/internal/session"
)
type sessionScreenModel struct {
list list.Model
sessions []sessionpkg.Window
width int
height int
err error
}
type sessionItem struct {
window sessionpkg.Window
}
func (i sessionItem) Title() string {
active := ""
if i.window.Active {
active = " active"
}
return fmt.Sprintf("%-28s #%d%s", truncate(i.window.ServerAlias, 28), i.window.Index, active)
}
func (i sessionItem) Description() string {
if i.window.StartedAt.IsZero() {
return "tmux window " + i.window.ID
}
return fmt.Sprintf("running %s · tmux %s", time.Since(i.window.StartedAt).Round(time.Second), i.window.ID)
}
func (i sessionItem) FilterValue() string {
return i.window.ServerAlias + " " + i.window.Name
}
func newSessionScreenModel(w, h int) *sessionScreenModel {
l := list.New([]list.Item{}, list.NewDefaultDelegate(), w, managerListHeight(h))
l.Title = "Sessions"
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
l.SetShowHelp(false)
l.Styles.Title = titleStyle
return &sessionScreenModel{list: l, width: w, height: h}
}
func (m *sessionScreenModel) loadSessions() tea.Cmd {
return func() tea.Msg {
windows, err := sessionpkg.List()
return sessionsLoadedMsg{sessions: windows, err: err, closed: true}
}
}
func (m *sessionScreenModel) setSessions(windows []sessionpkg.Window) {
m.sessions = windows
items := make([]list.Item, len(windows))
for index, window := range windows {
items[index] = sessionItem{window: window}
}
m.list.SetItems(items)
}
func (m *sessionScreenModel) selected() *sessionpkg.Window {
item, ok := m.list.SelectedItem().(sessionItem)
if !ok {
return nil
}
window := item.window
return &window
}
func (m *sessionScreenModel) closeSelected() tea.Cmd {
selected := m.selected()
if selected == nil {
return nil
}
id := selected.ID
return func() tea.Msg {
err := sessionpkg.Close(id)
windows, listErr := sessionpkg.List()
if err == nil {
err = listErr
}
return sessionsLoadedMsg{sessions: windows, err: err, closed: true}
}
}
func (m *sessionScreenModel) View() string {
notification := ""
if m.err != nil {
notification = errorStyle.Render(fmt.Sprintf("Error: %v", m.err))
}
body := func(width, height int) string {
if len(m.sessions) == 0 {
return renderPaddedPanel(width, height, []string{dashboardHelp("No active SSH sessions.")})
}
capacity := max(1, height-2)
start, end := visibleServerRange(len(m.sessions), m.list.Index(), max(1, capacity/2))
lines := make([]string, 0, capacity)
for index := start; index < end; index++ {
item := sessionItem{window: m.sessions[index]}
marker := " "
if index == m.list.Index() {
marker = "> "
}
lines = append(lines, marker+item.Title(), " "+item.Description())
}
return renderPaddedPanel(width, height, lines)
}
return renderScreenShell(screenShell{
breadcrumb: "Sessions",
status: fmt.Sprintf("%d active · tmux", len(m.sessions)),
notification: notification,
width: m.width,
height: m.height,
body: body,
footer: []helpItem{
{Key: "Enter", Action: "attach"},
{Key: "Ctrl+D (d)", Action: "close"},
{Key: "Ctrl+R (r)", Action: "refresh"},
{Key: "Ctrl+H", Action: "help"},
{Key: "Esc", Action: "back"},
},
})
}
type sessionsLoadedMsg struct {
sessions []sessionpkg.Window
err error
closed bool
}