-
Notifications
You must be signed in to change notification settings - Fork 0
/
tui.go
143 lines (127 loc) · 3 KB
/
tui.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package tui
import (
"fmt"
"github.com/bilguun0203/tailscale-tui/internal/tailscale"
"github.com/bilguun0203/tailscale-tui/internal/tui/constants"
nodedetails "github.com/bilguun0203/tailscale-tui/internal/tui/node_details"
nodelist "github.com/bilguun0203/tailscale-tui/internal/tui/node_list"
"github.com/charmbracelet/bubbles/spinner"
tea "github.com/charmbracelet/bubbletea"
)
type viewState int
const (
viewStateList viewState = iota
viewStateDetails
)
func (f viewState) String() string {
return [...]string{
"list",
"details",
}[f]
}
type Model struct {
viewState viewState
tsStatus tailscale.Status
selectedNodeID string
isLoading bool
err error
nodelist nodelist.Model
nodedetails nodedetails.Model
spinner spinner.Model
w, h int
}
type statusRequest struct {
status tailscale.Status
err error
}
type statusLoaded tailscale.Status
type statusError error
func getTsStatusAsync(c chan statusRequest) {
ts, err := tailscale.New()
if err != nil {
c <- statusRequest{status: tailscale.Status{}, err: err}
return
}
s, e := ts.Status()
c <- statusRequest{status: s, err: e}
}
func getTsStatus() tea.Cmd {
return func() tea.Msg {
c := make(chan statusRequest)
go getTsStatusAsync(c)
statusReq := <-c
if statusReq.err != nil {
return statusError(statusReq.err)
}
return statusLoaded(statusReq.status)
}
}
func (m Model) Init() tea.Cmd {
cmds := []tea.Cmd{
getTsStatus(),
m.spinner.Tick,
m.nodelist.Init(),
}
return tea.Batch(cmds...)
}
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
var cmds []tea.Cmd
switch msg := msg.(type) {
case statusLoaded:
m.tsStatus = tailscale.Status(msg)
m.nodelist = nodelist.New(&m.tsStatus, m.w, m.h)
m.isLoading = false
case statusError:
m.isLoading = false
m.err = msg
return m, tea.Quit
case nodedetails.BackMsg:
m.viewState = viewStateList
case nodelist.RefreshMsg:
cmd = getTsStatus()
cmds = append(cmds, cmd)
case nodelist.NodeSelectedMsg:
m.selectedNodeID = string(msg)
m.nodedetails = nodedetails.New(&m.tsStatus, m.selectedNodeID, m.w, m.h)
m.viewState = viewStateDetails
case tea.WindowSizeMsg:
m.w, m.h = msg.Width, msg.Height
if !m.isLoading {
m.nodelist.SetSize(msg.Width, msg.Height)
}
}
switch m.viewState {
case viewStateDetails:
m.nodedetails, cmd = m.nodedetails.Update(msg)
case viewStateList:
if m.isLoading {
m.spinner, cmd = m.spinner.Update(msg)
} else {
m.nodelist, cmd = m.nodelist.Update(msg)
}
}
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...)
}
func (m Model) View() string {
switch m.viewState {
case viewStateDetails:
return m.nodedetails.View()
default:
if m.isLoading {
return fmt.Sprintf("\n\n %s Loading ...\n\n", m.spinner.View())
}
return m.nodelist.View()
}
}
func New() Model {
m := Model{
viewState: viewStateList,
isLoading: true,
spinner: spinner.New(),
}
m.spinner.Spinner = spinner.Dot
m.spinner.Style = constants.SpinnerStyle
return m
}