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  }