mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore(cli): replace lipgloss with coder/pretty (#9564)
This change will improve over CLI performance and "snappiness" as well as substantially reduce our test times. Preliminary benchmarks show `coder server --help` times cut from 300ms to 120ms on my dogfood instance. The inefficiency of lipgloss disproportionately impacts our system, as all help text for every command is generated whenever any command is invoked. The `pretty` API could clean up a lot of the code (e.g., by replacing complex string concatenations with Printf), but this commit is too expansive as is so that work will be done in a follow up.
This commit is contained in:
+125
-36
@@ -2,11 +2,13 @@ package cliui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/charm/ui/common"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
var Canceled = xerrors.New("canceled")
|
||||
@@ -15,55 +17,142 @@ var Canceled = xerrors.New("canceled")
|
||||
var DefaultStyles Styles
|
||||
|
||||
type Styles struct {
|
||||
Bold,
|
||||
Checkmark,
|
||||
Code,
|
||||
Crossmark,
|
||||
DateTimeStamp,
|
||||
Error,
|
||||
Field,
|
||||
Keyword,
|
||||
Paragraph,
|
||||
Placeholder,
|
||||
Prompt,
|
||||
FocusedPrompt,
|
||||
Fuchsia,
|
||||
Logo,
|
||||
Warn,
|
||||
Wrap lipgloss.Style
|
||||
Wrap pretty.Style
|
||||
}
|
||||
|
||||
var color = termenv.NewOutput(os.Stdout).ColorProfile()
|
||||
|
||||
// TestColor sets the color profile to the given profile for the duration of the
|
||||
// test.
|
||||
// WARN: Must not be used in parallel tests.
|
||||
func TestColor(t *testing.T, tprofile termenv.Profile) {
|
||||
old := color
|
||||
color = tprofile
|
||||
t.Cleanup(func() {
|
||||
color = old
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
Green = color.Color("#04B575")
|
||||
Red = color.Color("#ED567A")
|
||||
Fuchsia = color.Color("#EE6FF8")
|
||||
Yellow = color.Color("#ECFD65")
|
||||
Blue = color.Color("#5000ff")
|
||||
)
|
||||
|
||||
func isTerm() bool {
|
||||
return color != termenv.Ascii
|
||||
}
|
||||
|
||||
// Bold returns a formatter that renders text in bold
|
||||
// if the terminal supports it.
|
||||
func Bold(s string) string {
|
||||
if !isTerm() {
|
||||
return s
|
||||
}
|
||||
return pretty.Sprint(pretty.Bold(), s)
|
||||
}
|
||||
|
||||
// BoldFmt returns a formatter that renders text in bold
|
||||
// if the terminal supports it.
|
||||
func BoldFmt() pretty.Formatter {
|
||||
if !isTerm() {
|
||||
return pretty.Style{}
|
||||
}
|
||||
return pretty.Bold()
|
||||
}
|
||||
|
||||
// Timestamp formats a timestamp for display.
|
||||
func Timestamp(t time.Time) string {
|
||||
return pretty.Sprint(DefaultStyles.DateTimeStamp, t.Format(time.Stamp))
|
||||
}
|
||||
|
||||
// Keyword formats a keyword for display.
|
||||
func Keyword(s string) string {
|
||||
return pretty.Sprint(DefaultStyles.Keyword, s)
|
||||
}
|
||||
|
||||
// Placeholder formats a placeholder for display.
|
||||
func Placeholder(s string) string {
|
||||
return pretty.Sprint(DefaultStyles.Placeholder, s)
|
||||
}
|
||||
|
||||
// Wrap prevents the text from overflowing the terminal.
|
||||
func Wrap(s string) string {
|
||||
return pretty.Sprint(DefaultStyles.Wrap, s)
|
||||
}
|
||||
|
||||
// Code formats code for display.
|
||||
func Code(s string) string {
|
||||
return pretty.Sprint(DefaultStyles.Code, s)
|
||||
}
|
||||
|
||||
// Field formats a field for display.
|
||||
func Field(s string) string {
|
||||
return pretty.Sprint(DefaultStyles.Field, s)
|
||||
}
|
||||
|
||||
func ifTerm(fmt pretty.Formatter) pretty.Formatter {
|
||||
if !isTerm() {
|
||||
return pretty.Nop
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
|
||||
func init() {
|
||||
lipgloss.SetDefaultRenderer(
|
||||
lipgloss.NewRenderer(os.Stdout, termenv.WithColorCache(true)),
|
||||
)
|
||||
|
||||
// All Styles are set after we change the DefaultRenderer so that the ColorCache
|
||||
// is in effect, mitigating the severe performance issue seen here:
|
||||
// https://github.com/coder/coder/issues/7884.
|
||||
|
||||
charmStyles := common.DefaultStyles()
|
||||
|
||||
// We do not adapt the color based on whether the terminal is light or dark.
|
||||
// Doing so would require a round-trip between the program and the terminal
|
||||
// due to the OSC query and response.
|
||||
DefaultStyles = Styles{
|
||||
Bold: lipgloss.NewStyle().Bold(true),
|
||||
Checkmark: charmStyles.Checkmark,
|
||||
Code: charmStyles.Code,
|
||||
Crossmark: charmStyles.Error.Copy().SetString("✘"),
|
||||
DateTimeStamp: charmStyles.LabelDim,
|
||||
Error: charmStyles.Error,
|
||||
Field: charmStyles.Code.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#000000", Dark: "#FFFFFF"}),
|
||||
Keyword: charmStyles.Keyword,
|
||||
Paragraph: charmStyles.Paragraph,
|
||||
Placeholder: lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "#585858", Dark: "#4d46b3"}),
|
||||
Prompt: charmStyles.Prompt.Copy().Foreground(lipgloss.AdaptiveColor{Light: "#9B9B9B", Dark: "#5C5C5C"}),
|
||||
FocusedPrompt: charmStyles.FocusedPrompt.Copy().Foreground(lipgloss.Color("#651fff")),
|
||||
Fuchsia: charmStyles.SelectedMenuItem.Copy(),
|
||||
Logo: charmStyles.Logo.Copy().SetString("Coder"),
|
||||
Warn: lipgloss.NewStyle().Foreground(
|
||||
lipgloss.AdaptiveColor{Light: "#04B575", Dark: "#ECFD65"},
|
||||
),
|
||||
Wrap: lipgloss.NewStyle().Width(80),
|
||||
Code: pretty.Style{
|
||||
ifTerm(pretty.XPad(1, 1)),
|
||||
pretty.FgColor(Red),
|
||||
pretty.BgColor(color.Color("#2c2c2c")),
|
||||
},
|
||||
DateTimeStamp: pretty.Style{
|
||||
pretty.FgColor(color.Color("#7571F9")),
|
||||
},
|
||||
Error: pretty.Style{
|
||||
pretty.FgColor(Red),
|
||||
},
|
||||
Field: pretty.Style{
|
||||
pretty.XPad(1, 1),
|
||||
pretty.FgColor(color.Color("#FFFFFF")),
|
||||
pretty.BgColor(color.Color("#2b2a2a")),
|
||||
},
|
||||
Keyword: pretty.Style{
|
||||
pretty.FgColor(Green),
|
||||
},
|
||||
Placeholder: pretty.Style{
|
||||
pretty.FgColor(color.Color("#4d46b3")),
|
||||
},
|
||||
Prompt: pretty.Style{
|
||||
pretty.FgColor(color.Color("#5C5C5C")),
|
||||
pretty.Wrap("> ", ""),
|
||||
},
|
||||
Warn: pretty.Style{
|
||||
pretty.FgColor(Yellow),
|
||||
},
|
||||
Wrap: pretty.Style{
|
||||
pretty.LineWrap(80),
|
||||
},
|
||||
}
|
||||
|
||||
DefaultStyles.FocusedPrompt = append(
|
||||
DefaultStyles.Prompt,
|
||||
pretty.FgColor(Blue),
|
||||
)
|
||||
}
|
||||
|
||||
// ValidateNotEmpty is a helper function to disallow empty inputs!
|
||||
|
||||
+7
-7
@@ -5,12 +5,12 @@ import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
// cliMessage provides a human-readable message for CLI errors and messages.
|
||||
type cliMessage struct {
|
||||
Style lipgloss.Style
|
||||
Style pretty.Style
|
||||
Header string
|
||||
Prefix string
|
||||
Lines []string
|
||||
@@ -21,13 +21,13 @@ func (m cliMessage) String() string {
|
||||
var str strings.Builder
|
||||
|
||||
if m.Prefix != "" {
|
||||
_, _ = str.WriteString(m.Style.Bold(true).Render(m.Prefix))
|
||||
_, _ = str.WriteString(Bold(m.Prefix))
|
||||
}
|
||||
|
||||
_, _ = str.WriteString(m.Style.Bold(false).Render(m.Header))
|
||||
pretty.Fprint(&str, m.Style, m.Header)
|
||||
_, _ = str.WriteString("\r\n")
|
||||
for _, line := range m.Lines {
|
||||
_, _ = fmt.Fprintf(&str, " %s %s\r\n", m.Style.Render("|"), line)
|
||||
_, _ = fmt.Fprintf(&str, " %s %s\r\n", pretty.Sprint(m.Style, "|"), line)
|
||||
}
|
||||
return str.String()
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func (m cliMessage) String() string {
|
||||
// Warn writes a log to the writer provided.
|
||||
func Warn(wtr io.Writer, header string, lines ...string) {
|
||||
_, _ = fmt.Fprint(wtr, cliMessage{
|
||||
Style: DefaultStyles.Warn.Copy(),
|
||||
Style: DefaultStyles.Warn,
|
||||
Prefix: "WARN: ",
|
||||
Header: header,
|
||||
Lines: lines,
|
||||
@@ -63,7 +63,7 @@ func Infof(wtr io.Writer, fmtStr string, args ...interface{}) {
|
||||
// Error writes a log to the writer provided.
|
||||
func Error(wtr io.Writer, header string, lines ...string) {
|
||||
_, _ = fmt.Fprint(wtr, cliMessage{
|
||||
Style: DefaultStyles.Error.Copy(),
|
||||
Style: DefaultStyles.Error,
|
||||
Prefix: "ERROR: ",
|
||||
Header: header,
|
||||
Lines: lines,
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.TemplateVersionParameter) (string, error) {
|
||||
@@ -16,10 +17,10 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
|
||||
}
|
||||
|
||||
if templateVersionParameter.Ephemeral {
|
||||
label += DefaultStyles.Warn.Render(" (build option)")
|
||||
label += pretty.Sprint(DefaultStyles.Warn, " (build option)")
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(inv.Stdout, DefaultStyles.Bold.Render(label))
|
||||
_, _ = fmt.Fprintln(inv.Stdout, Bold(label))
|
||||
|
||||
if templateVersionParameter.DescriptionPlaintext != "" {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+strings.TrimSpace(strings.Join(strings.Split(templateVersionParameter.DescriptionPlaintext, "\n"), "\n "))+"\n")
|
||||
@@ -45,7 +46,10 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+DefaultStyles.Prompt.String()+DefaultStyles.Field.Render(strings.Join(values, ", ")))
|
||||
pretty.Fprintf(
|
||||
inv.Stdout,
|
||||
DefaultStyles.Prompt, "%s\n", strings.Join(values, ", "),
|
||||
)
|
||||
value = string(v)
|
||||
}
|
||||
} else if len(templateVersionParameter.Options) > 0 {
|
||||
@@ -59,7 +63,7 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
|
||||
})
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintln(inv.Stdout)
|
||||
_, _ = fmt.Fprintln(inv.Stdout, " "+DefaultStyles.Prompt.String()+DefaultStyles.Field.Render(richParameterOption.Name))
|
||||
pretty.Fprintf(inv.Stdout, DefaultStyles.Prompt, "%s\n", richParameterOption.Name)
|
||||
value = richParameterOption.Value
|
||||
}
|
||||
} else {
|
||||
@@ -70,7 +74,7 @@ func RichParameter(inv *clibase.Invocation, templateVersionParameter codersdk.Te
|
||||
text += ":"
|
||||
|
||||
value, err = Prompt(inv, PromptOptions{
|
||||
Text: DefaultStyles.Bold.Render(text),
|
||||
Text: Bold(text),
|
||||
Validate: func(value string) error {
|
||||
return validateRichPrompt(value, templateVersionParameter)
|
||||
},
|
||||
|
||||
+12
-8
@@ -14,6 +14,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clibase"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
// PromptOptions supply a set of options to the prompt.
|
||||
@@ -55,21 +56,24 @@ func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprint(inv.Stdout, DefaultStyles.FocusedPrompt.String()+opts.Text+" ")
|
||||
pretty.Fprintf(inv.Stdout, DefaultStyles.FocusedPrompt, "")
|
||||
pretty.Fprintf(inv.Stdout, pretty.Nop, "%s ", opts.Text)
|
||||
if opts.IsConfirm {
|
||||
if len(opts.Default) == 0 {
|
||||
opts.Default = ConfirmYes
|
||||
}
|
||||
renderedYes := DefaultStyles.Placeholder.Render(ConfirmYes)
|
||||
renderedNo := DefaultStyles.Placeholder.Render(ConfirmNo)
|
||||
var (
|
||||
renderedYes = pretty.Sprint(DefaultStyles.Placeholder, ConfirmYes)
|
||||
renderedNo = pretty.Sprint(DefaultStyles.Placeholder, ConfirmNo)
|
||||
)
|
||||
if opts.Default == ConfirmYes {
|
||||
renderedYes = DefaultStyles.Bold.Render(ConfirmYes)
|
||||
renderedYes = Bold(ConfirmYes)
|
||||
} else {
|
||||
renderedNo = DefaultStyles.Bold.Render(ConfirmNo)
|
||||
renderedNo = Bold(ConfirmNo)
|
||||
}
|
||||
_, _ = fmt.Fprint(inv.Stdout, DefaultStyles.Placeholder.Render("("+renderedYes+DefaultStyles.Placeholder.Render("/"+renderedNo+DefaultStyles.Placeholder.Render(") "))))
|
||||
pretty.Fprintf(inv.Stdout, DefaultStyles.Placeholder, "(%s/%s)", renderedYes, renderedNo)
|
||||
} else if opts.Default != "" {
|
||||
_, _ = fmt.Fprint(inv.Stdout, DefaultStyles.Placeholder.Render("("+opts.Default+") "))
|
||||
_, _ = fmt.Fprint(inv.Stdout, pretty.Sprint(DefaultStyles.Placeholder, "("+opts.Default+") "))
|
||||
}
|
||||
interrupt := make(chan os.Signal, 1)
|
||||
|
||||
@@ -126,7 +130,7 @@ func Prompt(inv *clibase.Invocation, opts PromptOptions) (string, error) {
|
||||
if opts.Validate != nil {
|
||||
err := opts.Validate(line)
|
||||
if err != nil {
|
||||
_, _ = fmt.Fprintln(inv.Stdout, DefaultStyles.Error.Render(err.Error()))
|
||||
_, _ = fmt.Fprintln(inv.Stdout, pretty.Sprint(DefaultStyles.Error, err.Error()))
|
||||
return Prompt(inv, opts)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
func WorkspaceBuild(ctx context.Context, writer io.Writer, client *codersdk.Client, build uuid.UUID) error {
|
||||
@@ -54,7 +55,7 @@ func (err *ProvisionerJobError) Error() string {
|
||||
}
|
||||
|
||||
// ProvisionerJob renders a provisioner job with interactive cancellation.
|
||||
func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOptions) error {
|
||||
func ProvisionerJob(ctx context.Context, wr io.Writer, opts ProvisionerJobOptions) error {
|
||||
if opts.FetchInterval == 0 {
|
||||
opts.FetchInterval = time.Second
|
||||
}
|
||||
@@ -70,7 +71,7 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
|
||||
jobMutex sync.Mutex
|
||||
)
|
||||
|
||||
sw := &stageWriter{w: writer, verbose: opts.Verbose, silentLogs: opts.Silent}
|
||||
sw := &stageWriter{w: wr, verbose: opts.Verbose, silentLogs: opts.Silent}
|
||||
|
||||
printStage := func() {
|
||||
sw.Start(currentStage)
|
||||
@@ -127,7 +128,11 @@ func ProvisionerJob(ctx context.Context, writer io.Writer, opts ProvisionerJobOp
|
||||
return
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintf(writer, DefaultStyles.FocusedPrompt.String()+DefaultStyles.Bold.Render("Gracefully canceling...")+"\n\n")
|
||||
pretty.Fprintf(
|
||||
wr,
|
||||
DefaultStyles.FocusedPrompt.With(BoldFmt()),
|
||||
"Gracefully canceling...\n\n",
|
||||
)
|
||||
err := opts.Cancel()
|
||||
if err != nil {
|
||||
errChan <- xerrors.Errorf("cancel: %w", err)
|
||||
@@ -236,7 +241,7 @@ func (s *stageWriter) Log(createdAt time.Time, level codersdk.LogLevel, line str
|
||||
w = &s.logBuf
|
||||
}
|
||||
|
||||
render := func(s ...string) string { return strings.Join(s, " ") }
|
||||
var style pretty.Style
|
||||
|
||||
var lines []string
|
||||
if !createdAt.IsZero() {
|
||||
@@ -249,14 +254,14 @@ func (s *stageWriter) Log(createdAt time.Time, level codersdk.LogLevel, line str
|
||||
if !s.verbose {
|
||||
return
|
||||
}
|
||||
render = DefaultStyles.Placeholder.Render
|
||||
style = DefaultStyles.Placeholder
|
||||
case codersdk.LogLevelError:
|
||||
render = DefaultStyles.Error.Render
|
||||
style = DefaultStyles.Error
|
||||
case codersdk.LogLevelWarn:
|
||||
render = DefaultStyles.Warn.Render
|
||||
style = DefaultStyles.Warn
|
||||
case codersdk.LogLevelInfo:
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\n", render(lines...))
|
||||
pretty.Fprintf(w, style, "%s\n", strings.Join(lines, " "))
|
||||
}
|
||||
|
||||
func (s *stageWriter) flushLogs() {
|
||||
|
||||
+16
-15
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/pretty"
|
||||
)
|
||||
|
||||
type WorkspaceResourcesOptions struct {
|
||||
@@ -78,7 +79,7 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
|
||||
|
||||
// Display a line for the resource.
|
||||
tableWriter.AppendRow(table.Row{
|
||||
DefaultStyles.Bold.Render(resourceAddress),
|
||||
Bold(resourceAddress),
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
@@ -107,7 +108,7 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
|
||||
if totalAgents > 1 {
|
||||
sshCommand += "." + agent.Name
|
||||
}
|
||||
sshCommand = DefaultStyles.Code.Render(sshCommand)
|
||||
sshCommand = pretty.Sprint(DefaultStyles.Code, sshCommand)
|
||||
row = append(row, sshCommand)
|
||||
}
|
||||
tableWriter.AppendRow(row)
|
||||
@@ -122,31 +123,31 @@ func renderAgentStatus(agent codersdk.WorkspaceAgent) string {
|
||||
switch agent.Status {
|
||||
case codersdk.WorkspaceAgentConnecting:
|
||||
since := dbtime.Now().Sub(agent.CreatedAt)
|
||||
return DefaultStyles.Warn.Render("⦾ connecting") + " " +
|
||||
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
|
||||
return pretty.Sprint(DefaultStyles.Warn, "⦾ connecting") + " " +
|
||||
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]")
|
||||
case codersdk.WorkspaceAgentDisconnected:
|
||||
since := dbtime.Now().Sub(*agent.DisconnectedAt)
|
||||
return DefaultStyles.Error.Render("⦾ disconnected") + " " +
|
||||
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]")
|
||||
return pretty.Sprint(DefaultStyles.Error, "⦾ disconnected") + " " +
|
||||
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]")
|
||||
case codersdk.WorkspaceAgentTimeout:
|
||||
since := dbtime.Now().Sub(agent.CreatedAt)
|
||||
return fmt.Sprintf(
|
||||
"%s %s",
|
||||
DefaultStyles.Warn.Render("⦾ timeout"),
|
||||
DefaultStyles.Placeholder.Render("["+strconv.Itoa(int(since.Seconds()))+"s]"),
|
||||
pretty.Sprint(DefaultStyles.Warn, "⦾ timeout"),
|
||||
pretty.Sprint(DefaultStyles.Placeholder, "["+strconv.Itoa(int(since.Seconds()))+"s]"),
|
||||
)
|
||||
case codersdk.WorkspaceAgentConnected:
|
||||
return DefaultStyles.Keyword.Render("⦿ connected")
|
||||
return pretty.Sprint(DefaultStyles.Keyword, "⦿ connected")
|
||||
default:
|
||||
return DefaultStyles.Warn.Render("○ unknown")
|
||||
return pretty.Sprint(DefaultStyles.Warn, "○ unknown")
|
||||
}
|
||||
}
|
||||
|
||||
func renderAgentHealth(agent codersdk.WorkspaceAgent) string {
|
||||
if agent.Health.Healthy {
|
||||
return DefaultStyles.Keyword.Render("✔ healthy")
|
||||
return pretty.Sprint(DefaultStyles.Keyword, "✔ healthy")
|
||||
}
|
||||
return DefaultStyles.Error.Render("✘ " + agent.Health.Reason)
|
||||
return pretty.Sprint(DefaultStyles.Error, "✘ "+agent.Health.Reason)
|
||||
}
|
||||
|
||||
func renderAgentVersion(agentVersion, serverVersion string) string {
|
||||
@@ -154,11 +155,11 @@ func renderAgentVersion(agentVersion, serverVersion string) string {
|
||||
agentVersion = "(unknown)"
|
||||
}
|
||||
if !semver.IsValid(serverVersion) || !semver.IsValid(agentVersion) {
|
||||
return DefaultStyles.Placeholder.Render(agentVersion)
|
||||
return pretty.Sprint(DefaultStyles.Placeholder, agentVersion)
|
||||
}
|
||||
outdated := semver.Compare(agentVersion, serverVersion) < 0
|
||||
if outdated {
|
||||
return DefaultStyles.Warn.Render(agentVersion + " (outdated)")
|
||||
return pretty.Sprint(DefaultStyles.Warn, agentVersion+" (outdated)")
|
||||
}
|
||||
return DefaultStyles.Keyword.Render(agentVersion)
|
||||
return pretty.Sprint(DefaultStyles.Keyword, agentVersion)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user