github.com/wtfutil/wtf@v0.43.0/modules/digitalocean/widget.go (about) 1 package digitalocean 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 8 "github.com/digitalocean/godo" 9 "github.com/rivo/tview" 10 "github.com/wtfutil/wtf/utils" 11 "github.com/wtfutil/wtf/view" 12 "golang.org/x/oauth2" 13 ) 14 15 /* -------------------- Oauth2 Token -------------------- */ 16 17 type tokenSource struct { 18 AccessToken string 19 } 20 21 // Token creates and returns an Oauth2 token 22 func (t *tokenSource) Token() (*oauth2.Token, error) { 23 token := &oauth2.Token{ 24 AccessToken: t.AccessToken, 25 } 26 return token, nil 27 } 28 29 /* -------------------- Widget -------------------- */ 30 31 // Widget is the container for droplet data 32 type Widget struct { 33 view.ScrollableWidget 34 35 app *tview.Application 36 client *godo.Client 37 droplets []*Droplet 38 pages *tview.Pages 39 settings *Settings 40 41 err error 42 } 43 44 // NewWidget creates a new instance of a widget 45 func NewWidget(tviewApp *tview.Application, redrawChan chan bool, pages *tview.Pages, settings *Settings) *Widget { 46 widget := Widget{ 47 ScrollableWidget: view.NewScrollableWidget(tviewApp, redrawChan, pages, settings.Common), 48 49 app: tviewApp, 50 pages: pages, 51 settings: settings, 52 } 53 54 widget.initializeKeyboardControls() 55 56 widget.View.SetScrollable(true) 57 58 widget.SetRenderFunction(widget.display) 59 60 widget.createClient() 61 62 return &widget 63 } 64 65 /* -------------------- Exported Functions -------------------- */ 66 67 // Fetch retrieves droplet data 68 func (widget *Widget) Fetch() error { 69 if widget.client == nil { 70 return errors.New("client could not be initialized") 71 } 72 73 var err error 74 widget.droplets, err = widget.dropletsFetch() 75 return err 76 } 77 78 // Next selects the next item in the list 79 func (widget *Widget) Next() { 80 widget.ScrollableWidget.Next() 81 } 82 83 // Prev selects the previous item in the list 84 func (widget *Widget) Prev() { 85 widget.ScrollableWidget.Prev() 86 } 87 88 // Refresh updates the data for this widget and displays it onscreen 89 func (widget *Widget) Refresh() { 90 err := widget.Fetch() 91 if err != nil { 92 widget.err = err 93 widget.SetItemCount(0) 94 } else { 95 widget.err = nil 96 widget.SetItemCount(len(widget.droplets)) 97 } 98 99 widget.display() 100 } 101 102 // Unselect clears the selection of list items 103 func (widget *Widget) Unselect() { 104 widget.ScrollableWidget.Unselect() 105 widget.RenderFunction() 106 } 107 108 /* -------------------- Unexported Functions -------------------- */ 109 110 // createClient create a persisten DigitalOcean client for use in the calls below 111 func (widget *Widget) createClient() { 112 tokenSource := &tokenSource{ 113 AccessToken: widget.settings.apiKey, 114 } 115 116 oauthClient := oauth2.NewClient(context.Background(), tokenSource) 117 widget.client = godo.NewClient(oauthClient) 118 } 119 120 // currentDroplet returns the currently-selected droplet, if there is one 121 // Returns nil if no droplet is selected 122 func (widget *Widget) currentDroplet() *Droplet { 123 if len(widget.droplets) == 0 { 124 return nil 125 } 126 127 if len(widget.droplets) <= widget.Selected { 128 return nil 129 } 130 131 return widget.droplets[widget.Selected] 132 } 133 134 // dropletsFetch uses the DigitalOcean API to fetch information about all the available droplets 135 func (widget *Widget) dropletsFetch() ([]*Droplet, error) { 136 dropletList := []*Droplet{} 137 opts := &godo.ListOptions{} 138 139 for { 140 doDroplets, resp, err := widget.client.Droplets.List(context.Background(), opts) 141 if err != nil { 142 return dropletList, err 143 } 144 145 for _, doDroplet := range doDroplets { 146 droplet := NewDroplet(doDroplet) 147 dropletList = append(dropletList, droplet) 148 } 149 150 if resp.Links == nil || resp.Links.IsLastPage() { 151 break 152 } 153 154 page, err := resp.Links.CurrentPage() 155 if err != nil { 156 return dropletList, err 157 } 158 159 // Set the page we want for the next request 160 opts.Page = page + 1 161 } 162 163 return dropletList, nil 164 } 165 166 /* -------------------- Droplet Actions -------------------- */ 167 168 // dropletDestroy destroys the selected droplet 169 func (widget *Widget) dropletDestroy() { 170 currDroplet := widget.currentDroplet() 171 if currDroplet == nil { 172 return 173 } 174 175 _, err := widget.client.Droplets.Delete(context.Background(), currDroplet.ID) 176 if err != nil { 177 return 178 } 179 180 widget.dropletRemoveSelected() 181 widget.Refresh() 182 } 183 184 // dropletEnabledPrivateNetworking enabled private networking on the selected droplet 185 func (widget *Widget) dropletEnabledPrivateNetworking() { 186 currDroplet := widget.currentDroplet() 187 if currDroplet == nil { 188 return 189 } 190 191 _, _, err := widget.client.DropletActions.EnablePrivateNetworking(context.Background(), currDroplet.ID) 192 if err != nil { 193 return 194 } 195 196 widget.Refresh() 197 } 198 199 // dropletRemoveSelected removes the currently-selected droplet from the internal list of droplets 200 func (widget *Widget) dropletRemoveSelected() { 201 currDroplet := widget.currentDroplet() 202 if currDroplet != nil { 203 widget.droplets[len(widget.droplets)-1], widget.droplets[widget.Selected] = widget.droplets[widget.Selected], widget.droplets[len(widget.droplets)-1] 204 widget.droplets = widget.droplets[:len(widget.droplets)-1] 205 } 206 } 207 208 // dropletRestart restarts the selected droplet 209 func (widget *Widget) dropletRestart() { 210 currDroplet := widget.currentDroplet() 211 if currDroplet == nil { 212 return 213 } 214 215 _, _, err := widget.client.DropletActions.Reboot(context.Background(), currDroplet.ID) 216 if err != nil { 217 return 218 } 219 widget.Refresh() 220 } 221 222 // dropletShutDown powers down the selected droplet 223 func (widget *Widget) dropletShutDown() { 224 currDroplet := widget.currentDroplet() 225 if currDroplet == nil { 226 return 227 } 228 229 _, _, err := widget.client.DropletActions.Shutdown(context.Background(), currDroplet.ID) 230 if err != nil { 231 return 232 } 233 widget.Refresh() 234 } 235 236 /* -------------------- Common Actions -------------------- */ 237 238 // showInfo shows a modal window with information about the selected droplet 239 func (widget *Widget) showInfo() { 240 droplet := widget.currentDroplet() 241 if droplet == nil { 242 return 243 } 244 245 closeFunc := func() { 246 widget.pages.RemovePage("info") 247 widget.app.SetFocus(widget.View) 248 } 249 250 propTable := newDropletPropertiesTable(droplet).render() 251 propTable += utils.CenterText("Esc to close", 80) 252 253 modal := view.NewBillboardModal(propTable, closeFunc) 254 modal.SetTitle(fmt.Sprintf(" %s ", droplet.Name)) 255 256 widget.pages.AddPage("info", modal, false, true) 257 widget.app.SetFocus(modal) 258 259 widget.app.QueueUpdateDraw(func() { 260 widget.app.Draw() 261 }) 262 }