mirror of
https://github.com/coder/coder.git
synced 2025-07-08 11:39:50 +00:00
Use new table formatter everywhere (#3544)
This commit is contained in:
@ -90,7 +90,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
|
|||||||
sort = strings.ToLower(strings.ReplaceAll(sort, "_", " "))
|
sort = strings.ToLower(strings.ReplaceAll(sort, "_", " "))
|
||||||
h, ok := headersMap[sort]
|
h, ok := headersMap[sort]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", xerrors.Errorf("specified sort column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
|
return "", xerrors.Errorf(`specified sort column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Autocorrect
|
// Autocorrect
|
||||||
@ -101,7 +101,7 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
|
|||||||
column := strings.ToLower(strings.ReplaceAll(column, "_", " "))
|
column := strings.ToLower(strings.ReplaceAll(column, "_", " "))
|
||||||
h, ok := headersMap[column]
|
h, ok := headersMap[column]
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", xerrors.Errorf("specified filter column %q not found in table headers, available columns are %q", sort, strings.Join(headersRaw, `", "`))
|
return "", xerrors.Errorf(`specified filter column %q not found in table headers, available columns are "%v"`, sort, strings.Join(headersRaw, `", "`))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Autocorrect
|
// Autocorrect
|
||||||
@ -158,6 +158,10 @@ func DisplayTable(out any, sort string, filterColumns []string) (string, error)
|
|||||||
if val != nil {
|
if val != nil {
|
||||||
v = val.Format(time.Stamp)
|
v = val.Format(time.Stamp)
|
||||||
}
|
}
|
||||||
|
case fmt.Stringer:
|
||||||
|
if val != nil {
|
||||||
|
v = val.String()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rowSlice[i] = v
|
rowSlice[i] = v
|
||||||
@ -301,19 +305,3 @@ func valueToTableMap(val reflect.Value) (map[string]any, error) {
|
|||||||
|
|
||||||
return row, nil
|
return row, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateColumns(all, given []string) error {
|
|
||||||
for _, col := range given {
|
|
||||||
found := false
|
|
||||||
for _, c := range all {
|
|
||||||
if strings.EqualFold(strings.ReplaceAll(col, "_", " "), c) {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return fmt.Errorf("unknown column: %s", col)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package cliui_test
|
package cliui_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -12,6 +13,16 @@ import (
|
|||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type stringWrapper struct {
|
||||||
|
str string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fmt.Stringer = stringWrapper{}
|
||||||
|
|
||||||
|
func (s stringWrapper) String() string {
|
||||||
|
return s.str
|
||||||
|
}
|
||||||
|
|
||||||
type tableTest1 struct {
|
type tableTest1 struct {
|
||||||
Name string `table:"name"`
|
Name string `table:"name"`
|
||||||
NotIncluded string // no table tag
|
NotIncluded string // no table tag
|
||||||
@ -28,9 +39,9 @@ type tableTest1 struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type tableTest2 struct {
|
type tableTest2 struct {
|
||||||
Name string `table:"name"`
|
Name stringWrapper `table:"name"`
|
||||||
Age int `table:"age"`
|
Age int `table:"age"`
|
||||||
NotIncluded string `table:"-"`
|
NotIncluded string `table:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type tableTest3 struct {
|
type tableTest3 struct {
|
||||||
@ -48,21 +59,21 @@ func Test_DisplayTable(t *testing.T) {
|
|||||||
Age: 10,
|
Age: 10,
|
||||||
Roles: []string{"a", "b", "c"},
|
Roles: []string{"a", "b", "c"},
|
||||||
Sub1: tableTest2{
|
Sub1: tableTest2{
|
||||||
Name: "foo1",
|
Name: stringWrapper{str: "foo1"},
|
||||||
Age: 11,
|
Age: 11,
|
||||||
},
|
},
|
||||||
Sub2: &tableTest2{
|
Sub2: &tableTest2{
|
||||||
Name: "foo2",
|
Name: stringWrapper{str: "foo2"},
|
||||||
Age: 12,
|
Age: 12,
|
||||||
},
|
},
|
||||||
Sub3: tableTest3{
|
Sub3: tableTest3{
|
||||||
Sub: tableTest2{
|
Sub: tableTest2{
|
||||||
Name: "foo3",
|
Name: stringWrapper{str: "foo3"},
|
||||||
Age: 13,
|
Age: 13,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Sub4: tableTest2{
|
Sub4: tableTest2{
|
||||||
Name: "foo4",
|
Name: stringWrapper{str: "foo4"},
|
||||||
Age: 14,
|
Age: 14,
|
||||||
},
|
},
|
||||||
Time: someTime,
|
Time: someTime,
|
||||||
@ -73,18 +84,18 @@ func Test_DisplayTable(t *testing.T) {
|
|||||||
Age: 20,
|
Age: 20,
|
||||||
Roles: []string{"a"},
|
Roles: []string{"a"},
|
||||||
Sub1: tableTest2{
|
Sub1: tableTest2{
|
||||||
Name: "bar1",
|
Name: stringWrapper{str: "bar1"},
|
||||||
Age: 21,
|
Age: 21,
|
||||||
},
|
},
|
||||||
Sub2: nil,
|
Sub2: nil,
|
||||||
Sub3: tableTest3{
|
Sub3: tableTest3{
|
||||||
Sub: tableTest2{
|
Sub: tableTest2{
|
||||||
Name: "bar3",
|
Name: stringWrapper{str: "bar3"},
|
||||||
Age: 23,
|
Age: 23,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Sub4: tableTest2{
|
Sub4: tableTest2{
|
||||||
Name: "bar4",
|
Name: stringWrapper{str: "bar4"},
|
||||||
Age: 24,
|
Age: 24,
|
||||||
},
|
},
|
||||||
Time: someTime,
|
Time: someTime,
|
||||||
@ -95,18 +106,18 @@ func Test_DisplayTable(t *testing.T) {
|
|||||||
Age: 30,
|
Age: 30,
|
||||||
Roles: nil,
|
Roles: nil,
|
||||||
Sub1: tableTest2{
|
Sub1: tableTest2{
|
||||||
Name: "baz1",
|
Name: stringWrapper{str: "baz1"},
|
||||||
Age: 31,
|
Age: 31,
|
||||||
},
|
},
|
||||||
Sub2: nil,
|
Sub2: nil,
|
||||||
Sub3: tableTest3{
|
Sub3: tableTest3{
|
||||||
Sub: tableTest2{
|
Sub: tableTest2{
|
||||||
Name: "baz3",
|
Name: stringWrapper{str: "baz3"},
|
||||||
Age: 33,
|
Age: 33,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Sub4: tableTest2{
|
Sub4: tableTest2{
|
||||||
Name: "baz4",
|
Name: stringWrapper{str: "baz4"},
|
||||||
Age: 34,
|
Age: 34,
|
||||||
},
|
},
|
||||||
Time: someTime,
|
Time: someTime,
|
||||||
|
@ -5,12 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/cliui"
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/coder/cli/cliui"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -38,10 +36,6 @@ func featuresList() *cobra.Command {
|
|||||||
Use: "list",
|
Use: "list",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
err := cliui.ValidateColumns(featureColumns, columns)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
client, err := createClient(cmd)
|
client, err := createClient(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -54,11 +48,14 @@ func featuresList() *cobra.Command {
|
|||||||
out := ""
|
out := ""
|
||||||
switch outputFormat {
|
switch outputFormat {
|
||||||
case "table", "":
|
case "table", "":
|
||||||
out = displayFeatures(columns, entitlements.Features)
|
out, err = displayFeatures(columns, entitlements.Features)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("render table: %w", err)
|
||||||
|
}
|
||||||
case "json":
|
case "json":
|
||||||
outBytes, err := json.Marshal(entitlements)
|
outBytes, err := json.Marshal(entitlements)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("marshal users to JSON: %w", err)
|
return xerrors.Errorf("marshal features to JSON: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
out = string(outBytes)
|
out = string(outBytes)
|
||||||
@ -78,35 +75,28 @@ func featuresList() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type featureRow struct {
|
||||||
|
Name string `table:"name"`
|
||||||
|
Entitlement string `table:"entitlement"`
|
||||||
|
Enabled bool `table:"enabled"`
|
||||||
|
Limit *int64 `table:"limit"`
|
||||||
|
Actual *int64 `table:"actual"`
|
||||||
|
}
|
||||||
|
|
||||||
// displayFeatures will return a table displaying all features passed in.
|
// displayFeatures will return a table displaying all features passed in.
|
||||||
// filterColumns must be a subset of the feature fields and will determine which
|
// filterColumns must be a subset of the feature fields and will determine which
|
||||||
// columns to display
|
// columns to display
|
||||||
func displayFeatures(filterColumns []string, features map[string]codersdk.Feature) string {
|
func displayFeatures(filterColumns []string, features map[string]codersdk.Feature) (string, error) {
|
||||||
tableWriter := cliui.Table()
|
rows := make([]featureRow, 0, len(features))
|
||||||
header := table.Row{}
|
|
||||||
for _, h := range featureColumns {
|
|
||||||
header = append(header, h)
|
|
||||||
}
|
|
||||||
tableWriter.AppendHeader(header)
|
|
||||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
|
|
||||||
tableWriter.SortBy([]table.SortBy{{
|
|
||||||
Name: "username",
|
|
||||||
}})
|
|
||||||
for name, feat := range features {
|
for name, feat := range features {
|
||||||
tableWriter.AppendRow(table.Row{
|
rows = append(rows, featureRow{
|
||||||
name,
|
Name: name,
|
||||||
feat.Entitlement,
|
Entitlement: string(feat.Entitlement),
|
||||||
feat.Enabled,
|
Enabled: feat.Enabled,
|
||||||
intOrNil(feat.Limit),
|
Limit: feat.Limit,
|
||||||
intOrNil(feat.Actual),
|
Actual: feat.Actual,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return tableWriter.Render()
|
|
||||||
}
|
|
||||||
|
|
||||||
func intOrNil(i *int64) string {
|
return cliui.DisplayTable(rows, "name", filterColumns)
|
||||||
if i == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%d", *i)
|
|
||||||
}
|
}
|
||||||
|
102
cli/list.go
102
cli/list.go
@ -5,7 +5,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
@ -14,6 +13,49 @@ import (
|
|||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type workspaceListRow struct {
|
||||||
|
Workspace string `table:"workspace"`
|
||||||
|
Template string `table:"template"`
|
||||||
|
Status string `table:"status"`
|
||||||
|
LastBuilt string `table:"last built"`
|
||||||
|
Outdated bool `table:"outdated"`
|
||||||
|
StartsAt string `table:"starts at"`
|
||||||
|
StopsAfter string `table:"stops after"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func workspaceListRowFromWorkspace(now time.Time, usersByID map[uuid.UUID]codersdk.User, workspace codersdk.Workspace) workspaceListRow {
|
||||||
|
status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)
|
||||||
|
|
||||||
|
lastBuilt := now.UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
|
||||||
|
autostartDisplay := "-"
|
||||||
|
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
|
||||||
|
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
|
||||||
|
autostartDisplay = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
autostopDisplay := "-"
|
||||||
|
if !ptr.NilOrZero(workspace.TTLMillis) {
|
||||||
|
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
|
||||||
|
autostopDisplay = durationDisplay(dur)
|
||||||
|
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
|
||||||
|
remaining := time.Until(workspace.LatestBuild.Deadline)
|
||||||
|
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := usersByID[workspace.OwnerID]
|
||||||
|
return workspaceListRow{
|
||||||
|
Workspace: user.Username + "/" + workspace.Name,
|
||||||
|
Template: workspace.TemplateName,
|
||||||
|
Status: status,
|
||||||
|
LastBuilt: durationDisplay(lastBuilt),
|
||||||
|
Outdated: workspace.Outdated,
|
||||||
|
StartsAt: autostartDisplay,
|
||||||
|
StopsAfter: autostopDisplay,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func list() *cobra.Command {
|
func list() *cobra.Command {
|
||||||
var columns []string
|
var columns []string
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@ -32,10 +74,10 @@ func list() *cobra.Command {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if len(workspaces) == 0 {
|
if len(workspaces) == 0 {
|
||||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
|
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), cliui.Styles.Prompt.String()+"No workspaces found! Create one:")
|
||||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
|
||||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), " "+cliui.Styles.Code.Render("coder create <name>"))
|
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), " "+cliui.Styles.Code.Render("coder create <name>"))
|
||||||
_, _ = fmt.Fprintln(cmd.OutOrStdout())
|
_, _ = fmt.Fprintln(cmd.ErrOrStderr())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
users, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
|
users, err := client.Users(cmd.Context(), codersdk.UsersRequest{})
|
||||||
@ -47,48 +89,18 @@ func list() *cobra.Command {
|
|||||||
usersByID[user.ID] = user
|
usersByID[user.ID] = user
|
||||||
}
|
}
|
||||||
|
|
||||||
tableWriter := cliui.Table()
|
|
||||||
header := table.Row{"workspace", "template", "status", "last built", "outdated", "starts at", "stops after"}
|
|
||||||
tableWriter.AppendHeader(header)
|
|
||||||
tableWriter.SortBy([]table.SortBy{{
|
|
||||||
Name: "workspace",
|
|
||||||
}})
|
|
||||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, columns))
|
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
for _, workspace := range workspaces {
|
displayWorkspaces := make([]workspaceListRow, len(workspaces))
|
||||||
status := codersdk.WorkspaceDisplayStatus(workspace.LatestBuild.Job.Status, workspace.LatestBuild.Transition)
|
for i, workspace := range workspaces {
|
||||||
|
displayWorkspaces[i] = workspaceListRowFromWorkspace(now, usersByID, workspace)
|
||||||
lastBuilt := time.Now().UTC().Sub(workspace.LatestBuild.Job.CreatedAt).Truncate(time.Second)
|
|
||||||
autostartDisplay := "-"
|
|
||||||
if !ptr.NilOrEmpty(workspace.AutostartSchedule) {
|
|
||||||
if sched, err := schedule.Weekly(*workspace.AutostartSchedule); err == nil {
|
|
||||||
autostartDisplay = fmt.Sprintf("%s %s (%s)", sched.Time(), sched.DaysOfWeek(), sched.Location())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
autostopDisplay := "-"
|
|
||||||
if !ptr.NilOrZero(workspace.TTLMillis) {
|
|
||||||
dur := time.Duration(*workspace.TTLMillis) * time.Millisecond
|
|
||||||
autostopDisplay = durationDisplay(dur)
|
|
||||||
if !workspace.LatestBuild.Deadline.IsZero() && workspace.LatestBuild.Deadline.After(now) && status == "Running" {
|
|
||||||
remaining := time.Until(workspace.LatestBuild.Deadline)
|
|
||||||
autostopDisplay = fmt.Sprintf("%s (%s)", autostopDisplay, relative(remaining))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
user := usersByID[workspace.OwnerID]
|
|
||||||
tableWriter.AppendRow(table.Row{
|
|
||||||
user.Username + "/" + workspace.Name,
|
|
||||||
workspace.TemplateName,
|
|
||||||
status,
|
|
||||||
durationDisplay(lastBuilt),
|
|
||||||
workspace.Outdated,
|
|
||||||
autostartDisplay,
|
|
||||||
autostopDisplay,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), tableWriter.Render())
|
|
||||||
|
out, err := cliui.DisplayTable(displayWorkspaces, "workspace", columns)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/cliui"
|
|
||||||
"github.com/coder/coder/codersdk"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parameters() *cobra.Command {
|
func parameters() *cobra.Command {
|
||||||
@ -30,29 +26,3 @@ func parameters() *cobra.Command {
|
|||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// displayParameters will return a table displaying all parameters passed in.
|
|
||||||
// filterColumns must be a subset of the parameter fields and will determine which
|
|
||||||
// columns to display
|
|
||||||
func displayParameters(filterColumns []string, params ...codersdk.Parameter) string {
|
|
||||||
tableWriter := cliui.Table()
|
|
||||||
header := table.Row{"id", "scope", "scope id", "name", "source scheme", "destination scheme", "created at", "updated at"}
|
|
||||||
tableWriter.AppendHeader(header)
|
|
||||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
|
|
||||||
tableWriter.SortBy([]table.SortBy{{
|
|
||||||
Name: "name",
|
|
||||||
}})
|
|
||||||
for _, param := range params {
|
|
||||||
tableWriter.AppendRow(table.Row{
|
|
||||||
param.ID.String(),
|
|
||||||
param.Scope,
|
|
||||||
param.ScopeID.String(),
|
|
||||||
param.Name,
|
|
||||||
param.SourceScheme,
|
|
||||||
param.DestinationScheme,
|
|
||||||
param.CreatedAt,
|
|
||||||
param.UpdatedAt,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return tableWriter.Render()
|
|
||||||
}
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
|
"github.com/coder/coder/cli/cliui"
|
||||||
"github.com/coder/coder/codersdk"
|
"github.com/coder/coder/codersdk"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -70,11 +71,16 @@ func parameterList() *cobra.Command {
|
|||||||
return xerrors.Errorf("fetch params: %w", err)
|
return xerrors.Errorf("fetch params: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayParameters(columns, params...))
|
out, err := cliui.DisplayTable(params, "name", columns)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("render table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"name", "scope", "destination_scheme"},
|
cmd.Flags().StringArrayVarP(&columns, "column", "c", []string{"name", "scope", "destination scheme"},
|
||||||
"Specify a column to filter in the table.")
|
"Specify a column to filter in the table.")
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,17 @@ func templateList() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(templates) == 0 {
|
if len(templates) == 0 {
|
||||||
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s No templates found in %s! Create one:\n\n", caret, color.HiWhiteString(organization.Name))
|
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s No templates found in %s! Create one:\n\n", caret, color.HiWhiteString(organization.Name))
|
||||||
_, _ = fmt.Fprintln(cmd.OutOrStdout(), color.HiMagentaString(" $ coder templates create <directory>\n"))
|
_, _ = fmt.Fprintln(cmd.ErrOrStderr(), color.HiMagentaString(" $ coder templates create <directory>\n"))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayTemplates(columns, templates...))
|
out, err := displayTemplates(columns, templates...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ func TestTemplateList(t *testing.T) {
|
|||||||
|
|
||||||
pty := ptytest.New(t)
|
pty := ptytest.New(t)
|
||||||
cmd.SetIn(pty.Input())
|
cmd.SetIn(pty.Input())
|
||||||
cmd.SetOut(pty.Output())
|
cmd.SetErr(pty.Output())
|
||||||
|
|
||||||
errC := make(chan error)
|
errC := make(chan error)
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
"github.com/google/uuid"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/coder/coder/cli/cliui"
|
"github.com/coder/coder/cli/cliui"
|
||||||
@ -46,35 +46,41 @@ func templates() *cobra.Command {
|
|||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type templateTableRow struct {
|
||||||
|
Name string `table:"name"`
|
||||||
|
CreatedAt string `table:"created at"`
|
||||||
|
LastUpdated string `table:"last updated"`
|
||||||
|
OrganizationID uuid.UUID `table:"organization id"`
|
||||||
|
Provisioner codersdk.ProvisionerType `table:"provisioner"`
|
||||||
|
ActiveVersionID uuid.UUID `table:"active version id"`
|
||||||
|
UsedBy string `table:"used by"`
|
||||||
|
MaxTTL time.Duration `table:"max ttl"`
|
||||||
|
MinAutostartInterval time.Duration `table:"min autostart"`
|
||||||
|
}
|
||||||
|
|
||||||
// displayTemplates will return a table displaying all templates passed in.
|
// displayTemplates will return a table displaying all templates passed in.
|
||||||
// filterColumns must be a subset of the template fields and will determine which
|
// filterColumns must be a subset of the template fields and will determine which
|
||||||
// columns to display
|
// columns to display
|
||||||
func displayTemplates(filterColumns []string, templates ...codersdk.Template) string {
|
func displayTemplates(filterColumns []string, templates ...codersdk.Template) (string, error) {
|
||||||
tableWriter := cliui.Table()
|
rows := make([]templateTableRow, len(templates))
|
||||||
header := table.Row{
|
for i, template := range templates {
|
||||||
"Name", "Created At", "Last Updated", "Organization ID", "Provisioner",
|
|
||||||
"Active Version ID", "Used By", "Max TTL", "Min Autostart"}
|
|
||||||
tableWriter.AppendHeader(header)
|
|
||||||
tableWriter.SetColumnConfigs(cliui.FilterTableColumns(header, filterColumns))
|
|
||||||
tableWriter.SortBy([]table.SortBy{{
|
|
||||||
Name: "name",
|
|
||||||
}})
|
|
||||||
for _, template := range templates {
|
|
||||||
suffix := ""
|
suffix := ""
|
||||||
if template.WorkspaceOwnerCount != 1 {
|
if template.WorkspaceOwnerCount != 1 {
|
||||||
suffix = "s"
|
suffix = "s"
|
||||||
}
|
}
|
||||||
tableWriter.AppendRow(table.Row{
|
|
||||||
template.Name,
|
rows[i] = templateTableRow{
|
||||||
template.CreatedAt.Format("January 2, 2006"),
|
Name: template.Name,
|
||||||
template.UpdatedAt.Format("January 2, 2006"),
|
CreatedAt: template.CreatedAt.Format("January 2, 2006"),
|
||||||
template.OrganizationID.String(),
|
LastUpdated: template.UpdatedAt.Format("January 2, 2006"),
|
||||||
template.Provisioner,
|
OrganizationID: template.OrganizationID,
|
||||||
template.ActiveVersionID.String(),
|
Provisioner: template.Provisioner,
|
||||||
cliui.Styles.Fuchsia.Render(fmt.Sprintf("%d developer%s", template.WorkspaceOwnerCount, suffix)),
|
ActiveVersionID: template.ActiveVersionID,
|
||||||
(time.Duration(template.MaxTTLMillis) * time.Millisecond).String(),
|
UsedBy: cliui.Styles.Fuchsia.Render(fmt.Sprintf("%d developer%s", template.WorkspaceOwnerCount, suffix)),
|
||||||
(time.Duration(template.MinAutostartIntervalMillis) * time.Millisecond).String(),
|
MaxTTL: (time.Duration(template.MaxTTLMillis) * time.Millisecond),
|
||||||
})
|
MinAutostartInterval: (time.Duration(template.MinAutostartIntervalMillis) * time.Millisecond),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tableWriter.Render()
|
|
||||||
|
return cliui.DisplayTable(rows, "name", filterColumns)
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jedib0t/go-pretty/v6/table"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/xerrors"
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
@ -58,31 +58,44 @@ func templateVersionsList() *cobra.Command {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return xerrors.Errorf("get template versions by template: %w", err)
|
return xerrors.Errorf("get template versions by template: %w", err)
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintln(cmd.OutOrStdout(), displayTemplateVersions(template.ActiveVersionID, versions...))
|
|
||||||
|
out, err := displayTemplateVersions(template.ActiveVersionID, versions...)
|
||||||
|
if err != nil {
|
||||||
|
return xerrors.Errorf("render table: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = fmt.Fprintln(cmd.OutOrStdout(), out)
|
||||||
return err
|
return err
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type templateVersionRow struct {
|
||||||
|
Name string `table:"name"`
|
||||||
|
CreatedAt time.Time `table:"created at"`
|
||||||
|
CreatedBy string `table:"created by"`
|
||||||
|
Status string `table:"status"`
|
||||||
|
Active string `table:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
// displayTemplateVersions will return a table displaying existing
|
// displayTemplateVersions will return a table displaying existing
|
||||||
// template versions for the specified template.
|
// template versions for the specified template.
|
||||||
func displayTemplateVersions(activeVersionID uuid.UUID, templateVersions ...codersdk.TemplateVersion) string {
|
func displayTemplateVersions(activeVersionID uuid.UUID, templateVersions ...codersdk.TemplateVersion) (string, error) {
|
||||||
tableWriter := cliui.Table()
|
rows := make([]templateVersionRow, len(templateVersions))
|
||||||
header := table.Row{
|
for i, templateVersion := range templateVersions {
|
||||||
"Name", "Created At", "Created By", "Status", ""}
|
|
||||||
tableWriter.AppendHeader(header)
|
|
||||||
for _, templateVersion := range templateVersions {
|
|
||||||
var activeStatus = ""
|
var activeStatus = ""
|
||||||
if templateVersion.ID == activeVersionID {
|
if templateVersion.ID == activeVersionID {
|
||||||
activeStatus = cliui.Styles.Code.Render(cliui.Styles.Keyword.Render("Active"))
|
activeStatus = cliui.Styles.Code.Render(cliui.Styles.Keyword.Render("Active"))
|
||||||
}
|
}
|
||||||
tableWriter.AppendRow(table.Row{
|
|
||||||
templateVersion.Name,
|
rows[i] = templateVersionRow{
|
||||||
templateVersion.CreatedAt.Format("03:04:05 PM MST on Jan 2, 2006"),
|
Name: templateVersion.Name,
|
||||||
templateVersion.CreatedByName,
|
CreatedAt: templateVersion.CreatedAt,
|
||||||
strings.Title(string(templateVersion.Job.Status)),
|
CreatedBy: templateVersion.CreatedByName,
|
||||||
activeStatus,
|
Status: strings.Title(string(templateVersion.Job.Status)),
|
||||||
})
|
Active: activeStatus,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return tableWriter.Render()
|
|
||||||
|
return cliui.DisplayTable(rows, "name", nil)
|
||||||
}
|
}
|
||||||
|
@ -111,13 +111,13 @@ func userSingle() *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func displayUser(ctx context.Context, stderr io.Writer, client *codersdk.Client, user codersdk.User) string {
|
func displayUser(ctx context.Context, stderr io.Writer, client *codersdk.Client, user codersdk.User) string {
|
||||||
tableWriter := cliui.Table()
|
tw := cliui.Table()
|
||||||
addRow := func(name string, value interface{}) {
|
addRow := func(name string, value interface{}) {
|
||||||
key := ""
|
key := ""
|
||||||
if name != "" {
|
if name != "" {
|
||||||
key = name + ":"
|
key = name + ":"
|
||||||
}
|
}
|
||||||
tableWriter.AppendRow(table.Row{
|
tw.AppendRow(table.Row{
|
||||||
key, value,
|
key, value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -170,5 +170,5 @@ func displayUser(ctx context.Context, stderr io.Writer, client *codersdk.Client,
|
|||||||
addRow("Organizations", "(none)")
|
addRow("Organizations", "(none)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return tableWriter.Render()
|
return tw.Render()
|
||||||
}
|
}
|
||||||
|
@ -50,14 +50,14 @@ type ComputedParameter struct {
|
|||||||
|
|
||||||
// Parameter represents a set value for the scope.
|
// Parameter represents a set value for the scope.
|
||||||
type Parameter struct {
|
type Parameter struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id" table:"id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
Scope ParameterScope `json:"scope" table:"scope"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
ScopeID uuid.UUID `json:"scope_id" table:"scope id"`
|
||||||
Scope ParameterScope `json:"scope"`
|
Name string `json:"name" table:"name"`
|
||||||
ScopeID uuid.UUID `json:"scope_id"`
|
SourceScheme ParameterSourceScheme `json:"source_scheme" table:"source scheme"`
|
||||||
Name string `json:"name"`
|
DestinationScheme ParameterDestinationScheme `json:"destination_scheme" table:"destination scheme"`
|
||||||
SourceScheme ParameterSourceScheme `json:"source_scheme"`
|
CreatedAt time.Time `json:"created_at" table:"created at"`
|
||||||
DestinationScheme ParameterDestinationScheme `json:"destination_scheme"`
|
UpdatedAt time.Time `json:"updated_at" table:"updated at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ParameterSchema struct {
|
type ParameterSchema struct {
|
||||||
|
@ -205,13 +205,13 @@ export interface Pagination {
|
|||||||
// From codersdk/parameters.go
|
// From codersdk/parameters.go
|
||||||
export interface Parameter {
|
export interface Parameter {
|
||||||
readonly id: string
|
readonly id: string
|
||||||
readonly created_at: string
|
|
||||||
readonly updated_at: string
|
|
||||||
readonly scope: ParameterScope
|
readonly scope: ParameterScope
|
||||||
readonly scope_id: string
|
readonly scope_id: string
|
||||||
readonly name: string
|
readonly name: string
|
||||||
readonly source_scheme: ParameterSourceScheme
|
readonly source_scheme: ParameterSourceScheme
|
||||||
readonly destination_scheme: ParameterDestinationScheme
|
readonly destination_scheme: ParameterDestinationScheme
|
||||||
|
readonly created_at: string
|
||||||
|
readonly updated_at: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// From codersdk/parameters.go
|
// From codersdk/parameters.go
|
||||||
|
Reference in New Issue
Block a user