This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
package interaction
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"sync"
|
||||
"tunnel_pls/internal/config"
|
||||
"tunnel_pls/internal/random"
|
||||
"tunnel_pls/internal/session/slug"
|
||||
"tunnel_pls/types"
|
||||
|
||||
"github.com/charmbracelet/bubbles/help"
|
||||
"github.com/charmbracelet/bubbles/key"
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
type Interaction interface {
|
||||
Mode() types.InteractiveMode
|
||||
SetChannel(channel ssh.Channel)
|
||||
SetMode(m types.InteractiveMode)
|
||||
SetWH(w, h int)
|
||||
Start()
|
||||
Redraw()
|
||||
Send(message string) error
|
||||
}
|
||||
|
||||
type SessionRegistry interface {
|
||||
Update(user string, oldKey, newKey types.SessionKey) error
|
||||
}
|
||||
|
||||
type Forwarder interface {
|
||||
Close() error
|
||||
TunnelType() types.TunnelType
|
||||
ForwardedPort() uint16
|
||||
}
|
||||
|
||||
type CloseFunc func() error
|
||||
type interaction struct {
|
||||
randomizer random.Random
|
||||
config config.Config
|
||||
channel ssh.Channel
|
||||
slug slug.Slug
|
||||
forwarder Forwarder
|
||||
closeFunc CloseFunc
|
||||
user string
|
||||
sessionRegistry SessionRegistry
|
||||
program *tea.Program
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
mode types.InteractiveMode
|
||||
programMu sync.Mutex
|
||||
}
|
||||
|
||||
func (i *interaction) SetMode(m types.InteractiveMode) {
|
||||
i.mode = m
|
||||
}
|
||||
|
||||
func (i *interaction) Mode() types.InteractiveMode {
|
||||
return i.mode
|
||||
}
|
||||
|
||||
func (i *interaction) Send(message string) error {
|
||||
if i.channel != nil {
|
||||
_, err := i.channel.Write([]byte(message))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (i *interaction) SetWH(w, h int) {
|
||||
if i.program != nil {
|
||||
i.program.Send(tea.WindowSizeMsg{
|
||||
Width: w,
|
||||
Height: h,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func New(randomizer random.Random, config config.Config, slug slug.Slug, forwarder Forwarder, sessionRegistry SessionRegistry, user string, closeFunc CloseFunc) Interaction {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
return &interaction{
|
||||
randomizer: randomizer,
|
||||
config: config,
|
||||
channel: nil,
|
||||
slug: slug,
|
||||
forwarder: forwarder,
|
||||
closeFunc: closeFunc,
|
||||
user: user,
|
||||
sessionRegistry: sessionRegistry,
|
||||
program: nil,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *interaction) SetChannel(channel ssh.Channel) {
|
||||
i.channel = channel
|
||||
}
|
||||
|
||||
func (i *interaction) Stop() {
|
||||
if i.cancel != nil {
|
||||
i.cancel()
|
||||
}
|
||||
|
||||
i.programMu.Lock()
|
||||
defer i.programMu.Unlock()
|
||||
|
||||
if i.program != nil {
|
||||
i.program.Kill()
|
||||
i.program = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tickMsg:
|
||||
m.showingComingSoon = false
|
||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink)
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
m.commandList.SetWidth(msg.Width)
|
||||
m.commandList.SetHeight(msg.Height - 4)
|
||||
|
||||
if msg.Width < 80 {
|
||||
m.slugInput.Width = msg.Width - 10
|
||||
} else {
|
||||
m.slugInput.Width = 50
|
||||
}
|
||||
return m, nil
|
||||
|
||||
case tea.QuitMsg:
|
||||
m.quitting = true
|
||||
return m, tea.Batch(tea.ClearScreen, textinput.Blink, tea.Quit)
|
||||
|
||||
case tea.KeyMsg:
|
||||
if m.showingComingSoon {
|
||||
return m.comingSoonUpdate(msg)
|
||||
}
|
||||
|
||||
if m.editingSlug {
|
||||
return m.slugUpdate(msg)
|
||||
}
|
||||
|
||||
if m.showingCommands {
|
||||
return m.commandsUpdate(msg)
|
||||
}
|
||||
|
||||
return m.dashboardUpdate(msg)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (i *interaction) Redraw() {
|
||||
if i.program != nil {
|
||||
i.program.Send(tea.ClearScreen())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *model) View() string {
|
||||
if m.quitting {
|
||||
return ""
|
||||
}
|
||||
|
||||
if m.showingComingSoon {
|
||||
return m.comingSoonView()
|
||||
}
|
||||
|
||||
if m.editingSlug {
|
||||
return m.slugView()
|
||||
}
|
||||
|
||||
if m.showingCommands {
|
||||
return m.commandsView()
|
||||
}
|
||||
|
||||
return m.dashboardView()
|
||||
}
|
||||
|
||||
func (i *interaction) Start() {
|
||||
if i.mode == types.InteractiveModeHEADLESS {
|
||||
return
|
||||
}
|
||||
lipgloss.SetColorProfile(termenv.TrueColor)
|
||||
|
||||
protocol := "http"
|
||||
if i.config.TLSEnabled() {
|
||||
protocol = "https"
|
||||
}
|
||||
|
||||
tunnelType := i.forwarder.TunnelType()
|
||||
port := i.forwarder.ForwardedPort()
|
||||
|
||||
items := []list.Item{
|
||||
commandItem{name: "slug", desc: "Set custom subdomain"},
|
||||
commandItem{name: "tunnel-type", desc: "Change tunnel type (Coming Soon)"},
|
||||
}
|
||||
|
||||
delegate := list.NewDefaultDelegate()
|
||||
delegate.ShowDescription = true
|
||||
delegate.SetHeight(2)
|
||||
|
||||
commandList := list.New(items, delegate, 80, 20)
|
||||
commandList.Title = "Select a command"
|
||||
commandList.SetShowStatusBar(false)
|
||||
commandList.SetFilteringEnabled(false)
|
||||
commandList.SetShowHelp(false)
|
||||
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = "my-custom-slug"
|
||||
ti.CharLimit = 20
|
||||
ti.Width = 50
|
||||
|
||||
m := &model{
|
||||
randomizer: i.randomizer,
|
||||
domain: i.config.Domain(),
|
||||
protocol: protocol,
|
||||
tunnelType: tunnelType,
|
||||
port: port,
|
||||
commandList: commandList,
|
||||
slugInput: ti,
|
||||
interaction: i,
|
||||
keymap: keymap{
|
||||
quit: key.NewBinding(
|
||||
key.WithKeys("q", "ctrl+c"),
|
||||
key.WithHelp("q", "quit"),
|
||||
),
|
||||
command: key.NewBinding(
|
||||
key.WithKeys("c"),
|
||||
key.WithHelp("c", "commands"),
|
||||
),
|
||||
random: key.NewBinding(
|
||||
key.WithKeys("ctrl+r"),
|
||||
key.WithHelp("ctrl+r", "random"),
|
||||
),
|
||||
},
|
||||
help: help.New(),
|
||||
}
|
||||
|
||||
i.programMu.Lock()
|
||||
i.program = tea.NewProgram(
|
||||
m,
|
||||
tea.WithInput(i.channel),
|
||||
tea.WithOutput(i.channel),
|
||||
tea.WithAltScreen(),
|
||||
tea.WithMouseCellMotion(),
|
||||
tea.WithoutSignals(),
|
||||
tea.WithoutSignalHandler(),
|
||||
tea.WithFPS(30),
|
||||
)
|
||||
i.programMu.Unlock()
|
||||
|
||||
_, err := i.program.Run()
|
||||
if err != nil {
|
||||
log.Printf("Cannot close tea: %s \n", err)
|
||||
}
|
||||
|
||||
i.programMu.Lock()
|
||||
if i.program != nil {
|
||||
i.program.Kill()
|
||||
i.program = nil
|
||||
}
|
||||
i.programMu.Unlock()
|
||||
|
||||
if i.closeFunc != nil {
|
||||
_ = i.closeFunc()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user