Files
tailscale-vpn/internal/tui/update.go
2026-04-10 01:46:57 +09:00

345 lines
8.0 KiB
Go

package tui
import (
"errors"
"tailscale-vpn/internal/pages"
"tailscale-vpn/internal/services"
tea "charm.land/bubbletea/v2"
)
func (m TUIInterface) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case pages.HomePageMsg:
m.Page = pageHome
m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers)
return m, nil
case pages.SettingsPageMsg:
m.Page = pageSettings
m.SettingsEditMode = ""
m.SettingsConfirmDelete = false
m.SettingsSelected = 0
return m, loadServersCmd(m.Services)
case pages.SelectServerPageMsg:
m.Page = pageSelectServer
m.SelectServerServers = m.SettingsServers
m.SelectServerSelected = 0
for i, srv := range m.SelectServerServers {
if m.HasSelectedServer && srv.ID == m.SelectedServer.ID {
m.SelectServerSelected = i
break
}
}
return m, nil
case pages.ServerSelectedMsg:
m.SelectedServer = msg.Server
m.HasSelectedServer = msg.Server.ID != ""
if msg.Err != nil {
m.VPNError = msg.Err
}
return m, func() tea.Msg { return pages.HomePageMsg{} }
case pages.ServerSelectionCanceledMsg:
return m, func() tea.Msg { return pages.HomePageMsg{} }
case pages.ServerListLoadedMsg:
m.SettingsServers = msg.Servers
m.HasServers = len(msg.Servers) > 0
if msg.Err != nil {
m.VPNError = msg.Err
}
return m, nil
case pages.ServerEditMsg:
m.SettingsEditMode = msg.Mode
m.SettingsConfirmDelete = false
if msg.Mode == "edit" {
m.SettingsEditingServer = msg.Server
m.SettingsFormName = msg.Server.Name
m.SettingsFormHost = msg.Server.Host
} else {
m.SettingsEditingServer = services.Server{}
m.SettingsFormName = ""
m.SettingsFormHost = ""
}
m.SettingsFormField = 0
return m, nil
case pages.ServerSaveMsg:
m.SettingsEditMode = ""
if msg.Err != nil {
m.VPNError = msg.Err
}
return m, loadServersCmd(m.Services)
case pages.ServerDeleteMsg:
m.SettingsConfirmDelete = false
if msg.Err != nil {
m.VPNError = msg.Err
} else {
if m.SettingsSelected >= len(m.SettingsServers) {
m.SettingsSelected = max(len(m.SettingsServers)-1, 0)
}
}
return m, loadServersCmd(m.Services)
case tea.WindowSizeMsg:
m.WindowWidth = msg.Width
m.WindowHeight = msg.Height
return m, nil
case tea.KeyPressMsg:
if m.Page == pageHome {
return m.updateHomePage(msg)
}
if m.Page == pageSettings {
return m.updateSettingsPage(msg)
}
if m.Page == pageSelectServer {
return m.updateSelectServerPage(msg)
}
case tea.PasteMsg:
if m.Page == pageSettings && m.SettingsEditMode != "" {
if m.SettingsFormField == 0 {
m.SettingsFormName += msg.Content
} else if m.SettingsFormField == 1 {
m.SettingsFormHost += msg.Content
}
return m, nil
}
case pages.VPNStatusMsg:
m.VPNConnected = msg.Connected
m.VPNLoading = false
if msg.Err != nil {
m.VPNError = msg.Err
} else {
m.VPNError = nil
}
m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers)
return m, nil
case pages.VPNToggleMsg:
m.VPNConnected = msg.Connected
m.VPNToggleLoading = false
if msg.Err != nil {
m.VPNError = msg.Err
} else {
m.VPNError = nil
}
m.MenuItems = pages.HomeMenuItems(m.VPNConnected, m.HasServers)
return m, nil
}
return m, nil
}
func (m TUIInterface) updateHomePage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
if m.VPNLoading || m.VPNToggleLoading {
if msg.String() == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
return m, nil
}
switch msg.String() {
case "up", "k":
if m.Selected > 0 {
m.Selected--
}
return m, nil
case "down", "j":
if m.Selected < len(m.MenuItems)-1 {
m.Selected++
}
return m, nil
case "enter":
if len(m.MenuItems) > 0 {
switch m.MenuItems[m.Selected].Key {
case "select-server":
return m, func() tea.Msg { return pages.SelectServerPageMsg{} }
case "settings":
return m, func() tea.Msg { return pages.SettingsPageMsg{} }
case "on":
m.VPNToggleLoading = true
m.VPNError = nil
return m, turnOnVPNCmd(m.Services)
case "off":
m.VPNToggleLoading = true
m.VPNError = nil
return m, turnOffVPNCmd(m.Services)
}
}
return m, nil
case "r":
m.VPNLoading = true
m.VPNError = nil
return m, checkVPNStatusCmd(m.Services)
case "ctrl+c", "esc":
m.Quitting = true
return m, tea.Quit
}
return m, nil
}
func (m TUIInterface) updateSelectServerPage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
switch msg.String() {
case "up", "k":
if m.SelectServerSelected > 0 {
m.SelectServerSelected--
}
return m, nil
case "down", "j":
if m.SelectServerSelected < len(m.SelectServerServers)-1 {
m.SelectServerSelected++
}
return m, nil
case "enter":
if m.SelectServerSelected >= 0 && m.SelectServerSelected < len(m.SelectServerServers) {
return m, selectServerCmd(m.Services, m.SelectServerServers[m.SelectServerSelected].ID)
}
return m, nil
case "esc", "ctrl+c":
return m, func() tea.Msg { return pages.ServerSelectionCanceledMsg{} }
}
return m, nil
}
func (m TUIInterface) updateSettingsPage(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
if m.SettingsEditMode != "" {
switch msg.String() {
case "esc":
m.SettingsEditMode = ""
return m, nil
case "enter":
if m.SettingsFormField == 2 {
if m.SettingsFormName == "" || m.SettingsFormHost == "" {
m.VPNError = errors.New("name and host are required")
return m, nil
}
if m.SettingsEditMode == "add" {
return m, addServerCmd(m.Services, m.SettingsFormName, m.SettingsFormHost)
}
return m, updateServerCmd(m.Services, m.SettingsEditingServer.ID, m.SettingsFormName, m.SettingsFormHost)
} else if m.SettingsFormField == 3 {
m.SettingsEditMode = ""
return m, nil
} else {
m.SettingsFormField = (m.SettingsFormField + 1) % 4
return m, nil
}
case "up", "k":
m.SettingsFormField = (m.SettingsFormField - 1 + 4) % 4
return m, nil
case "down", "j":
m.SettingsFormField = (m.SettingsFormField + 1) % 4
return m, nil
case "tab":
m.SettingsFormField = (m.SettingsFormField + 1) % 4
return m, nil
case "backtab":
m.SettingsFormField = (m.SettingsFormField - 1 + 4) % 4
return m, nil
case "backspace":
if m.SettingsFormField == 0 && len(m.SettingsFormName) > 0 {
m.SettingsFormName = m.SettingsFormName[:len(m.SettingsFormName)-1]
} else if m.SettingsFormField == 1 && len(m.SettingsFormHost) > 0 {
m.SettingsFormHost = m.SettingsFormHost[:len(m.SettingsFormHost)-1]
}
return m, nil
case "ctrl+c":
m.Quitting = true
return m, tea.Quit
default:
if m.SettingsFormField == 0 || m.SettingsFormField == 1 {
if msg.Text != "" {
if m.SettingsFormField == 0 {
m.SettingsFormName += msg.Text
} else {
m.SettingsFormHost += msg.Text
}
}
}
return m, nil
}
}
switch msg.String() {
case "esc":
if m.SettingsConfirmDelete {
m.SettingsConfirmDelete = false
return m, nil
}
return m, func() tea.Msg { return pages.HomePageMsg{} }
case "up", "k":
if m.SettingsConfirmDelete {
return m, nil
}
if m.SettingsSelected > 0 {
m.SettingsSelected--
}
return m, nil
case "down", "j":
if m.SettingsConfirmDelete {
return m, nil
}
if m.SettingsSelected < len(m.SettingsServers)-1 {
m.SettingsSelected++
}
return m, nil
case "a":
if m.SettingsConfirmDelete {
return m, nil
}
return m, func() tea.Msg { return pages.ServerEditMsg{Mode: "add"} }
case "enter":
if m.SettingsConfirmDelete {
if m.SettingsSelected >= 0 && m.SettingsSelected < len(m.SettingsServers) {
return m, deleteServerCmd(m.Services, m.SettingsServers[m.SettingsSelected].ID)
}
m.SettingsConfirmDelete = false
return m, nil
}
if m.SettingsSelected >= 0 && m.SettingsSelected < len(m.SettingsServers) {
return m, func() tea.Msg {
return pages.ServerEditMsg{
Mode: "edit",
Server: m.SettingsServers[m.SettingsSelected],
}
}
}
return m, nil
case "d":
if m.SettingsConfirmDelete {
return m, nil
}
if len(m.SettingsServers) > 0 {
m.SettingsConfirmDelete = true
}
return m, nil
case "ctrl+c":
m.Quitting = true
return m, tea.Quit
}
return m, nil
}