github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/list.go (about)

     1  package ddevapp
     2  
     3  import (
     4  	"bytes"
     5  	"time"
     6  
     7  	"github.com/ddev/ddev/pkg/globalconfig"
     8  	"github.com/ddev/ddev/pkg/nodeps"
     9  	"github.com/ddev/ddev/pkg/output"
    10  	"github.com/ddev/ddev/pkg/styles"
    11  	"github.com/ddev/ddev/pkg/util"
    12  	"github.com/jedib0t/go-pretty/v6/table"
    13  	"github.com/jedib0t/go-pretty/v6/text"
    14  )
    15  
    16  // ListCommandSettings conains all filters and settings of the `ddev list` command
    17  type ListCommandSettings struct {
    18  	// ActiveOnly, if set, shows only running projects
    19  	ActiveOnly bool
    20  
    21  	// Continuous, if set, makes list continuously output
    22  	Continuous bool
    23  
    24  	// WrapListTable allow that the text in the table of ddev list wraps instead of cutting it to fit the terminal width
    25  	WrapTableText bool
    26  
    27  	// ContinuousSleepTime is time to sleep between reads with --continuous
    28  	ContinuousSleepTime int
    29  
    30  	// TypeFilter contains the project type which is then used to filter the project list
    31  	TypeFilter string
    32  }
    33  
    34  // List provides the functionality for `ddev list`
    35  // activeOnly if true only shows projects that are currently Docker containers
    36  // continuous if true keeps requesting and outputting continuously
    37  // wrapTableText if true the text is wrapped instead of truncated to fit the row length
    38  // continuousSleepTime is the time between reports
    39  func List(settings ListCommandSettings) {
    40  	defer util.TimeTrack()()
    41  
    42  	var out bytes.Buffer
    43  
    44  	for {
    45  		apps, err := GetProjects(settings.ActiveOnly)
    46  		if err != nil {
    47  			util.Failed("Failed getting GetProjects: %v", err)
    48  		}
    49  
    50  		appDescs := make([]map[string]interface{}, 0)
    51  
    52  		if len(apps) < 1 {
    53  			output.UserOut.WithField("raw", appDescs).Println("No DDEV projects were found.")
    54  		} else {
    55  			t := CreateAppTable(&out, settings.WrapTableText)
    56  			for _, app := range apps {
    57  				// Filter by project type
    58  				if settings.TypeFilter != "" && settings.TypeFilter != app.Type {
    59  					continue
    60  				}
    61  
    62  				desc, err := app.Describe(true)
    63  				if err != nil {
    64  					util.Error("Failed to describe project %s: %v", app.GetName(), err)
    65  				}
    66  				appDescs = append(appDescs, desc)
    67  				RenderAppRow(t, desc)
    68  			}
    69  
    70  			routerStatus, _ := GetRouterStatus()
    71  			var extendedRouterStatus = RenderRouterStatus()
    72  			if nodeps.ArrayContainsString(globalconfig.DdevGlobalConfig.OmitContainersGlobal, globalconfig.DdevRouterContainer) {
    73  				extendedRouterStatus = "disabled"
    74  			}
    75  			router := globalconfig.DdevGlobalConfig.Router
    76  			t.AppendFooter(table.Row{
    77  				"Router", routerStatus, "~/.ddev", globalconfig.GetRouterURL(), router},
    78  			)
    79  			t.Render()
    80  			output.UserOut.WithField("raw", appDescs).Print(out.String())
    81  			if routerStatus != "healthy" {
    82  				rawResult := map[string]string{
    83  					"routerStatus":         routerStatus,
    84  					"extendedRouterStatus": extendedRouterStatus,
    85  				}
    86  				rawResult["routerStatus"] = routerStatus
    87  				rawResult["extendedStatus"] = extendedRouterStatus
    88  				output.UserOut.WithField("raw", rawResult)
    89  			}
    90  		}
    91  
    92  		if !settings.Continuous {
    93  			break
    94  		}
    95  
    96  		time.Sleep(time.Duration(settings.ContinuousSleepTime) * time.Second)
    97  	}
    98  }
    99  
   100  // CreateAppTable will create a new app table for describe and list output
   101  func CreateAppTable(out *bytes.Buffer, wrapTableText bool) table.Writer {
   102  	t := table.NewWriter()
   103  	t.AppendHeader(table.Row{"Name", "Status", "Location", "URL", "Type"})
   104  	termWidth, _ := nodeps.GetTerminalWidthHeight()
   105  	usableWidth := termWidth - 15
   106  	statusWidth := 7 // Maybe just "running"
   107  	nameWidth := 10
   108  	typeWidth := 9 // drupal7, magento2 or wordpress
   109  	locationWidth := 20
   110  	urlWidth := 20
   111  	if termWidth > 80 {
   112  		urlWidth = urlWidth + (termWidth-80)/2
   113  		locationWidth = locationWidth + (termWidth-80)/2
   114  		statusWidth = statusWidth + (termWidth-80)/3
   115  	}
   116  	totUsedWidth := nameWidth + statusWidth + locationWidth + urlWidth + typeWidth
   117  	if !wrapTableText {
   118  		t.SetAllowedRowLength(termWidth)
   119  	}
   120  
   121  	util.Debug("Detected terminal width=%v usableWidth=%d statusWidth=%d nameWidth=%d locationWidth=%d urlWidth=%d typeWidth=%d totUsedWidth=%d", termWidth, usableWidth, statusWidth, nameWidth, locationWidth, urlWidth, typeWidth, totUsedWidth)
   122  	t.SortBy([]table.SortBy{{Name: "Name"}})
   123  
   124  	if !globalconfig.DdevGlobalConfig.SimpleFormatting {
   125  
   126  		t.SetColumnConfigs([]table.ColumnConfig{
   127  			{
   128  				Name: "Name",
   129  				//WidthMax: nameWidth,
   130  			},
   131  			{
   132  				Name:     "Status",
   133  				WidthMax: statusWidth,
   134  			},
   135  			{
   136  				Name: "Location",
   137  				//WidthMax: locationWidth,
   138  			},
   139  			{
   140  				Name: "URL",
   141  				//WidthMax: urlWidth,
   142  			},
   143  			{
   144  				Name:             "Type",
   145  				WidthMax:         int(typeWidth),
   146  				WidthMaxEnforcer: text.WrapText,
   147  			},
   148  		})
   149  	}
   150  	styles.SetGlobalTableStyle(t)
   151  	t.SetOutputMirror(out)
   152  	return t
   153  }