From 9af94a00dd4e356599a5726f84741b7376d3a2de Mon Sep 17 00:00:00 2001 From: krls Date: Tue, 23 Apr 2024 10:33:56 +0200 Subject: [PATCH 1/2] change colors and add logout, support info, env section, update texts and ux --- src/cmd/env.go | 37 +++++++++++ src/cmd/logout.go | 42 ++++++++++++ src/cmd/root.go | 52 ++++++++------- src/cmd/support.go | 33 ++++++++++ src/cmd/vpnDown.go | 2 +- src/cmd/vpnUp.go | 4 +- src/i18n/en.go | 26 ++++++-- src/i18n/i18n.go | 14 ++++ src/uxBlock/styles/styles.go | 122 +++++++++++++++-------------------- src/uxBlock/table.go | 2 +- 10 files changed, 230 insertions(+), 104 deletions(-) create mode 100644 src/cmd/env.go create mode 100644 src/cmd/logout.go create mode 100644 src/cmd/support.go diff --git a/src/cmd/env.go b/src/cmd/env.go new file mode 100644 index 00000000..66bbe958 --- /dev/null +++ b/src/cmd/env.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "context" + "fmt" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/constants" + "github.com/zeropsio/zcli/src/uxBlock" + "github.com/zeropsio/zcli/src/uxBlock/styles" +) + +var env string + +func envCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("env"). + Short(i18n.T(i18n.CmdDescEnv)). + HelpFlag(i18n.T(i18n.CmdHelpEnv)). + GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { + + fmt.Println(styles.CobraSectionColor().SetString("Global Env Variables:").String() + ` +` + styles.CobraItemNameColor().SetString(constants.CliLogFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliLogFilePathEnvVar) + ` +` + styles.CobraItemNameColor().SetString(constants.CliDataFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliDataFilePathEnvVar) + ` +` + styles.CobraItemNameColor().SetString(constants.CliTerminalMode).String() + ` ` + i18n.T(i18n.CliTerminalModeEnvVar)) + +fmt.Println(styles.CobraSectionColor().SetString(` +Curently used variables:`).String()) + + body := &uxBlock.TableBody{} + guestInfoPart(body) + cmdData.UxBlocks.Table(body) + + return nil + }) +} \ No newline at end of file diff --git a/src/cmd/logout.go b/src/cmd/logout.go new file mode 100644 index 00000000..d8759f95 --- /dev/null +++ b/src/cmd/logout.go @@ -0,0 +1,42 @@ +package cmd + +import ( + "context" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/cliStorage" + "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/uxBlock/styles" +) + +var logout string + +func logoutCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("logout"). + Short(i18n.T(i18n.CmdDescLogout)). + HelpFlag(i18n.T(i18n.CmdHelpLogout)). + LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { + + uxBlocks := cmdData.UxBlocks + + _, err := cmdData.RestApiClient.PostAuthLogout(ctx) + if err != nil { + return err + } + + _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data { + return cliStorage.Data{} + }) + if err != nil { + return err + } + + uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.LogoutVpnDisconnecting))) + disconnectVpn(ctx, cmdData.UxBlocks) // TODO: ask - there is no need for any declaration - i can just call this function from anywhere? + uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.LogoutSuccess))) + + return nil + + }) +} \ No newline at end of file diff --git a/src/cmd/root.go b/src/cmd/root.go index 9c48ead9..c84696ff 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -25,6 +25,7 @@ func rootCmd() *cmdBuilder.Cmd { SetHelpTemplate(getRootTemplate()). SilenceError(true). AddChildrenCmd(loginCmd()). + AddChildrenCmd(logoutCmd()). AddChildrenCmd(versionCmd()). AddChildrenCmd(scopeCmd()). AddChildrenCmd(projectCmd()). @@ -32,14 +33,22 @@ func rootCmd() *cmdBuilder.Cmd { AddChildrenCmd(vpnCmd()). AddChildrenCmd(statusShowDebugLogsCmd()). AddChildrenCmd(servicePushCmd()). + AddChildrenCmd(envCmd()). + AddChildrenCmd(supportCmd()). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { - body := &uxBlock.TableBody{} + fmt.Println(`Welcome to zCli by Zerops! - body.AddStringsRow(i18n.T(i18n.StatusInfoLoggedUser), "-") +To unlock the full potential of zCLI, you need to log in using your Zerops account. +Logging in enables you to access various features and interact with Zerops services seamlessly. - guestInfoPart(body) +To log in, simply use the following command: zcli login +Replace with the authentication token generated from your Zerops account. +Once logged in, you'll be able to manage projects, deploy applications, configure VPN, +and much more directly from the command line interface. - cmdData.UxBlocks.Table(body) +If you encounter any issues during the login process or have any questions, +feel free to find out how to contact our support team by running 'zcli support'. +`) // print the default command help cmdData.PrintHelp() @@ -47,7 +56,6 @@ func rootCmd() *cmdBuilder.Cmd { return nil }). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { - body := &uxBlock.TableBody{} var loggedUser string if info, err := cmdData.RestApiClient.GetUserInfo(ctx); err != nil { @@ -60,10 +68,8 @@ func rootCmd() *cmdBuilder.Cmd { } } - body.AddStringsRow(i18n.T(i18n.StatusInfoLoggedUser), loggedUser) - - guestInfoPart(body) + // TODO: krls - check whole block if cmdData.CliStorage.Data().ScopeProjectId.Filled() { // project scope is set projectId, _ := cmdData.CliStorage.Data().ScopeProjectId.Get() @@ -75,20 +81,21 @@ func rootCmd() *cmdBuilder.Cmd { return err } } else { - body.AddStringsRow(i18n.T(i18n.ScopedProject), err.Error()) + fmt.Print(i18n.T(i18n.ScopedProject), err.Error()) } } else { - body.AddStringsRow(i18n.T(i18n.ScopedProject), fmt.Sprintf("%s [%s]", project.Name.String(), project.ID.Native())) + fmt.Print(i18n.T(i18n.ScopedProject), fmt.Sprintf("%s [%s]", project.Name.String(), project.ID.Native())) } } + var vpnStatusText string if isVpnUp(ctx, cmdData.UxBlocks, 1) { - body.AddStringsRow(i18n.T(i18n.StatusInfoVpnStatus), i18n.T(i18n.VpnCheckingConnectionIsActive)) + vpnStatusText = i18n.T(i18n.VpnCheckingConnectionIsActive) } else { - body.AddStringsRow(i18n.T(i18n.StatusInfoVpnStatus), i18n.T(i18n.VpnCheckingConnectionIsNotActive)) + vpnStatusText = i18n.T(i18n.VpnCheckingConnectionIsNotActive) } - cmdData.UxBlocks.Table(body) + fmt.Printf("Welcome in Zerops!\nYou are loged as %s \nand your %s.\n\n", loggedUser, vpnStatusText) // print the default command help cmdData.PrintHelp() @@ -119,23 +126,23 @@ func guestInfoPart(tableBody *uxBlock.TableBody) { func getRootTemplate() string { return styles.CobraSectionColor().SetString("Usage:").String() + `{{if .Runnable}} - {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} - {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} +{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} +{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} ` + styles.CobraSectionColor().SetString("Aliases:").String() + ` - {{.NameAndAliases}}{{end}}{{if .HasExample}} +{{.NameAndAliases}}{{end}}{{if .HasExample}} ` + styles.CobraSectionColor().SetString("Examples:").String() + ` {{.Example}}{{end}}{{if .HasAvailableSubCommands}}{{$cmds := .Commands}}{{if eq (len .Groups) 0}} ` + styles.CobraSectionColor().SetString("Available Commands:").String() + `{{range $cmds}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - ` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} +` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{else}}{{range $group := .Groups}} {{.Title}}{{range $cmds}}{{if (and (eq .GroupID $group.ID) (or .IsAvailableCommand (eq .Name "help")))}} - ` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} +` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{if not .AllChildCommandsHaveGroup}} ` + styles.CobraSectionColor().SetString("Additional Commands:").String() + `{{range $cmds}}{{if (and (eq .GroupID "") (or .IsAvailableCommand (eq .Name "help")))}} - ` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} +` + styles.CobraItemNameColor().SetString("{{rpad .Name .NamePadding }}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} ` + styles.CobraSectionColor().SetString("Flags:").String() + ` {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} @@ -144,12 +151,7 @@ func getRootTemplate() string { {{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} ` + styles.CobraSectionColor().SetString("Additional help topics:").String() + `{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - ` + styles.CobraItemNameColor().SetString("{{rpad .CommandPath .CommandPathPadding}}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -` + styles.CobraSectionColor().SetString("Global Env Variables:").String() + ` - ` + styles.CobraItemNameColor().SetString(constants.CliLogFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliLogFilePathEnvVar) + ` - ` + styles.CobraItemNameColor().SetString(constants.CliDataFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliDataFilePathEnvVar) + ` - ` + styles.CobraItemNameColor().SetString(constants.CliTerminalMode).String() + ` ` + i18n.T(i18n.CliTerminalModeEnvVar) + ` +` + styles.CobraItemNameColor().SetString("{{rpad .CommandPath .CommandPathPadding}}").String() + ` {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} ` diff --git a/src/cmd/support.go b/src/cmd/support.go new file mode 100644 index 00000000..a872c333 --- /dev/null +++ b/src/cmd/support.go @@ -0,0 +1,33 @@ +package cmd + +import ( + "context" + "fmt" + + "github.com/zeropsio/zcli/src/cmdBuilder" + "github.com/zeropsio/zcli/src/i18n" +) + +var support string + +func supportCmd() *cmdBuilder.Cmd { + return cmdBuilder.NewCmd(). + Use("support"). + Short(i18n.T(i18n.CmdDescSupport)). + HelpFlag(i18n.T(i18n.CmdHelpSupport)). + GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { + + + fmt.Println("You can contact Zerops support via:") + fmt.Println("- E-mail: team@zerops.io") + fmt.Println("- Discord: https://discord.com/invite/WDvCZ54") + fmt.Println(` +Additionally, you can explore our documentation +at https://docs.zerops.io/references/cli for further details. + `) + return nil + + return nil + }) +} + diff --git a/src/cmd/vpnDown.go b/src/cmd/vpnDown.go index 744cdacb..7d2214b3 100644 --- a/src/cmd/vpnDown.go +++ b/src/cmd/vpnDown.go @@ -48,7 +48,7 @@ func disconnectVpn(ctx context.Context, uxBlocks uxBlock.UxBlocks) error { return err } - uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.VpnDown))) + uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.VpnDown))) return nil } diff --git a/src/cmd/vpnUp.go b/src/cmd/vpnUp.go index 1bc2fce8..bdb99575 100644 --- a/src/cmd/vpnUp.go +++ b/src/cmd/vpnUp.go @@ -132,13 +132,15 @@ func vpnUpCmd() *cmdBuilder.Cmd { return err } + // wait for the vpn to be up if isVpnUp(ctx, uxBlocks, 6) { - uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.VpnUp))) + uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.VpnUp))) } else { uxBlocks.PrintWarning(styles.WarningLine(i18n.T(i18n.VpnPingFailed))) } + return nil }) } diff --git a/src/i18n/en.go b/src/i18n/en.go index ba447a15..fd90f67d 100644 --- a/src/i18n/en.go +++ b/src/i18n/en.go @@ -5,11 +5,17 @@ import "fmt" var en = map[string]string{ // login CmdHelpLogin: "the login command.", - CmdDescLogin: "Logs you into Zerops. Use a generated Zerops token or your login e-mail and password.", + CmdDescLogin: "Login into Zerops with generated Zerops token", LoginSuccess: "You are logged as %s <%s>", RegionNotFound: "Selected region %s not found", RegionTableColumnName: "Name", + // logout + CmdHelpLogout: "the logout command.", + CmdDescLogout: "Disconnect from VPN and log out from your Zerops account", + LogoutVpnDisconnecting: "Disconnecting from VPN. Please provide your password if prompted.", + LogoutSuccess: "Successfully logged out. You are now disconnected from Zerops services.", + // scope CmdHelpScope: "the scope command.", CmdDescScope: "Scope commands group", @@ -24,7 +30,7 @@ var en = map[string]string{ // project CmdHelpProject: "the project command.", - CmdDescProject: "Project commands group.", + CmdDescProject: "Project commands group", // project lit CmdHelpProjectList: "the project list command.", @@ -52,7 +58,7 @@ var en = map[string]string{ // service CmdHelpService: "the service command.", - CmdDescService: "Zerops service commands group.", + CmdDescService: "Zerops service commands group", // service start CmdHelpServiceStart: "the service start command.", @@ -112,7 +118,7 @@ var en = map[string]string{ // push CmdHelpPush: "the service push command.", - CmdDescPush: "Builds your application in Zerops and deploys it.", + CmdDescPush: "Builds your application in Zerops and deploys it", CmdDescPushLong: "Builds your application in Zerops and deploys it. \n\n" + "The command triggers the build pipeline defined in zerops.yml. Zerops.yml must be in the working\n" + "directory. The working directory is by default the current directory and can be changed\n" + @@ -153,12 +159,20 @@ var en = map[string]string{ // status show debug logs CmdHelpStatusShowDebugLogs: "the status show debug logs command.", - CmdDescStatusShowDebugLogs: "Shows zCLI debug logs.", + CmdDescStatusShowDebugLogs: "Shows zCLI debug logs", DebugLogsNotFound: "Debug logs not found", // version CmdHelpVersion: "the version command.", - CmdDescVersion: "Shows the current zCLI version.", + CmdDescVersion: "Shows the current zCLI version", + + // support + CmdHelpSupport: "the support command.", + CmdDescSupport: "How to contact Zerops support for assistance", + + // env + CmdHelpEnv: "the support command.", + CmdDescEnv: "Displays global environment variables, their paths and additional options", // vpn CmdHelpVpn: "the vpn command.", diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index d36b404a..80c2a3a0 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -23,6 +23,12 @@ const ( RegionNotFound = "RegionNotFound" RegionTableColumnName = "RegionTableColumnName" + // logout + CmdHelpLogout = "CmdHelpLogout" + CmdDescLogout = "CmdDescLogout" + LogoutVpnDisconnecting = "LogoutVpnDisconnecting" + LogoutSuccess = "LogoutSuccess" + // scope CmdHelpScope = "CmdHelpScope" CmdDescScope = "CmdDescScope" @@ -156,6 +162,14 @@ const ( CmdHelpVersion = "CmdHelpVersion" CmdDescVersion = "CmdDescVersion" + // support + CmdHelpSupport = "CmdHelpSupport" + CmdDescSupport = "CmdDescSupport" + + // support + CmdHelpEnv = "CmdHelpEnv" + CmdDescEnv = "CmdDescEnv" + // vpn CmdHelpVpn = "CmdHelpVpn" CmdDescVpn = "CmdDescVpn" diff --git a/src/uxBlock/styles/styles.go b/src/uxBlock/styles/styles.go index 6abba119..c33de521 100644 --- a/src/uxBlock/styles/styles.go +++ b/src/uxBlock/styles/styles.go @@ -7,13 +7,40 @@ import ( ) const ( + // SYMBOLS SuccessIcon = "✔" ErrorIcon = "✗" SelectIcon = "➔" InfoIcon = "➤" WarningIcon = "!" + + // COLORS + prefixTextColorLight = "15" + prefixTextColorDark = "16" + + successColorLight = "28" + successColorDark = "10" + + warningColorLight = "142" + warningColorDark = "11" + + selectColorLight = "33" + selectColorDark = "45" + + infoColorLight = "16" + infoColorDark = "15" + + errorColorLight = "196" + errorColorDark = "196" + + cobraSectionColorLight = "4" + cobraSectionColorDark = "139" + + cobraItemColorLight = "5" + cobraItemColorDark = "51" ) + var defaultRender = lipgloss.NewRenderer(os.Stdout) func defaultStyle() lipgloss.Style { @@ -22,128 +49,80 @@ func defaultStyle() lipgloss.Style { func SuccessPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#FFFFFF", ANSI256: "231", ANSI: "97"}, - Dark: lipgloss.CompleteColor{TrueColor: "#000000", ANSI256: "0", ANSI: "0"}, - }). - Background(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#66bb6a", ANSI256: "114", ANSI: "32"}, - Dark: lipgloss.CompleteColor{TrueColor: "#66bb6a", ANSI256: "114", ANSI: "32"}, - }). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: successColorLight, Dark: successColorDark}). PaddingLeft(1).PaddingRight(1). SetString("DONE") } func SuccessColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#66bb6a", ANSI256: "114", ANSI: "32"}, - Dark: lipgloss.CompleteColor{TrueColor: "#66bb6a", ANSI256: "114", ANSI: "32"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: successColorLight, Dark: successColorDark}) } func ErrorPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#FFFFFF", ANSI256: "231", ANSI: "97"}, - Dark: lipgloss.CompleteColor{TrueColor: "#000000", ANSI256: "0", ANSI: "0"}, - }). - Background(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#ff1b16", ANSI256: "196", ANSI: "91"}, - Dark: lipgloss.CompleteColor{TrueColor: "#ff1b16", ANSI256: "196", ANSI: "91"}, - }). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: errorColorLight, Dark: errorColorDark}). PaddingLeft(1).PaddingRight(1). SetString("ERR") } func ErrorColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#ff1b16", ANSI256: "196", ANSI: "91"}, - Dark: lipgloss.CompleteColor{TrueColor: "#ff1b16", ANSI256: "196", ANSI: "91"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: errorColorLight, Dark: errorColorDark}) } func WarningPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#FFFFFF", ANSI256: "231", ANSI: "97"}, - Dark: lipgloss.CompleteColor{TrueColor: "#000000", ANSI256: "0", ANSI: "0"}, - }). - Background(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#ffa726", ANSI256: "216", ANSI: "93"}, - Dark: lipgloss.CompleteColor{TrueColor: "#ffa726", ANSI256: "216", ANSI: "93"}, - }). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}). PaddingLeft(1).PaddingRight(1). SetString("WARN") } func WarningColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#ffa726", ANSI256: "216", ANSI: "93"}, - Dark: lipgloss.CompleteColor{TrueColor: "#ffa726", ANSI256: "216", ANSI: "93"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}) } func InfoPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#FFFFFF", ANSI256: "231", ANSI: "97"}, - Dark: lipgloss.CompleteColor{TrueColor: "#000000", ANSI256: "0", ANSI: "0"}, - }). - Background(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#e6e7ec", ANSI256: "231", ANSI: "37"}, - Dark: lipgloss.CompleteColor{TrueColor: "#e6e7ec", ANSI256: "231", ANSI: "37"}, - }). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: infoColorLight, Dark: infoColorDark}). PaddingLeft(1).PaddingRight(1). SetString("INFO") } func InfoColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#e6e7ec", ANSI256: "231", ANSI: "37"}, - Dark: lipgloss.CompleteColor{TrueColor: "#e6e7ec", ANSI256: "231", ANSI: "37"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: infoColorLight, Dark: infoColorDark}) } func SelectPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#FFFFFF", ANSI256: "231", ANSI: "97"}, - Dark: lipgloss.CompleteColor{TrueColor: "#000000", ANSI256: "0", ANSI: "0"}, - }). - Background(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#07c", ANSI256: "27", ANSI: "30"}, - Dark: lipgloss.CompleteColor{TrueColor: "#07c", ANSI256: "27", ANSI: "30"}, - }). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: selectColorLight, Dark: selectColorDark}). PaddingLeft(1).PaddingRight(1). SetString("SELECT") } func SelectColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#07c", ANSI256: "27", ANSI: "30"}, - Dark: lipgloss.CompleteColor{TrueColor: "#07c", ANSI256: "27", ANSI: "30"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: selectColorLight, Dark: selectColorDark}) } func CobraSectionColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#cc0077", ANSI256: "162", ANSI: "31"}, - Dark: lipgloss.CompleteColor{TrueColor: "#cc0077", ANSI256: "162", ANSI: "31"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}). + BorderBottom(true). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}) } func CobraItemNameColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.CompleteAdaptiveColor{ - Light: lipgloss.CompleteColor{TrueColor: "#00ccbb", ANSI256: "44", ANSI: "36"}, - Dark: lipgloss.CompleteColor{TrueColor: "#00ccbb", ANSI256: "44", ANSI: "36"}, - }) + Foreground(lipgloss.AdaptiveColor{Light: cobraItemColorLight, Dark: cobraItemColorDark}) } func DialogBox() lipgloss.Style { @@ -168,11 +147,14 @@ func DialogButton() lipgloss.Style { func ActiveDialogButton() lipgloss.Style { return DialogButton(). - Foreground(SelectPrefix(). - GetForeground()). + Foreground(SelectPrefix().GetForeground()). Background(SelectPrefix().GetBackground()) } +func TableBorderStyle() lipgloss.Style { + return InfoColor() +} + func TableRow() lipgloss.Style { return InfoColor(). PaddingLeft(1). diff --git a/src/uxBlock/table.go b/src/uxBlock/table.go index 9f6e7898..764b5ae6 100644 --- a/src/uxBlock/table.go +++ b/src/uxBlock/table.go @@ -90,7 +90,7 @@ func (b *uxBlocks) Table(body *TableBody, auxOptions ...TableOption) { } t := table.New(). - BorderStyle(styles.InfoColor()). + BorderStyle(styles.TableBorderStyle()). Border(lipgloss.NormalBorder()). StyleFunc(func(row, col int) lipgloss.Style { return styles.TableRow() From 466b009f6da89046b8f5397e0bb4d99cea47cfe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Hellmann?= Date: Wed, 29 May 2024 19:35:22 +0200 Subject: [PATCH 2/2] improvement: add cmd text to i18n package & stdout handling & error handling --- Makefile | 12 ++++++- cmd/zcli/main.go | 6 +--- go.sum | 4 +++ src/cmd/env.go | 52 ++++++++++++++++++--------- src/cmd/logout.go | 49 ++++++++++++------------- src/cmd/root.go | 61 +++++++++----------------------- src/cmd/support.go | 24 +++++-------- src/cmd/version.go | 3 +- src/cmd/vpnUp.go | 2 -- src/cmdBuilder/createRunFunc.go | 6 ++++ src/cmdBuilder/executeRootCmd.go | 48 +++++-------------------- src/i18n/en.go | 36 +++++++++++++++---- src/i18n/i18n.go | 16 +++++++-- src/logger/handler.go | 2 +- src/logger/hooks.go | 17 ++++++--- src/printer/printer.go | 48 +++++++++++++++++++++++++ src/storage/handler.go | 7 ++++ src/terminal/terminal.go | 43 ++++++++++++++++++++++ src/uxBlock/styles/line.go | 6 ++-- src/uxBlock/styles/styles.go | 59 +++++++++++++++--------------- 20 files changed, 301 insertions(+), 200 deletions(-) create mode 100644 src/printer/printer.go create mode 100644 src/terminal/terminal.go diff --git a/Makefile b/Makefile index f15fda0c..720b0ed4 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,18 @@ ## help show this help .PHONY: help +define helpMessage +possible values: + test + lint + build-for-windows-amd + build-for-linux-amd + build-for-darwin-arm +endef +export helpMessage + help: - @printf "possible values: test, lint" + @echo "$$helpMessage" test: go test -v ./cmd/... ./src/... diff --git a/cmd/zcli/main.go b/cmd/zcli/main.go index 9879f005..e59f6eaa 100644 --- a/cmd/zcli/main.go +++ b/cmd/zcli/main.go @@ -1,13 +1,9 @@ package main import ( - "os" - "github.com/zeropsio/zcli/src/cmd" ) func main() { - if cmd.ExecuteCmd() != nil { - os.Exit(1) - } + cmd.ExecuteCmd() } diff --git a/go.sum b/go.sum index afcc5c3c..c5a497dc 100644 --- a/go.sum +++ b/go.sum @@ -115,6 +115,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE= golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= @@ -144,6 +146,8 @@ golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.18.0 h1:k8NLag8AGHnn+PHbl7g43CtqZAwG60vZkLqgyZgIHgQ= +golang.org/x/tools v0.18.0/go.mod h1:GL7B4CwcLLeo59yx/9UWWuNOW1n3VZ4f5axWfML7Lcg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/src/cmd/env.go b/src/cmd/env.go index 66bbe958..e360321d 100644 --- a/src/cmd/env.go +++ b/src/cmd/env.go @@ -2,36 +2,54 @@ package cmd import ( "context" - "fmt" "github.com/zeropsio/zcli/src/cmdBuilder" - "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/constants" + "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/printer" "github.com/zeropsio/zcli/src/uxBlock" "github.com/zeropsio/zcli/src/uxBlock/styles" ) -var env string - func envCmd() *cmdBuilder.Cmd { return cmdBuilder.NewCmd(). Use("env"). Short(i18n.T(i18n.CmdDescEnv)). HelpFlag(i18n.T(i18n.CmdHelpEnv)). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { - - fmt.Println(styles.CobraSectionColor().SetString("Global Env Variables:").String() + ` -` + styles.CobraItemNameColor().SetString(constants.CliLogFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliLogFilePathEnvVar) + ` -` + styles.CobraItemNameColor().SetString(constants.CliDataFilePathEnvVar).String() + ` ` + i18n.T(i18n.CliDataFilePathEnvVar) + ` -` + styles.CobraItemNameColor().SetString(constants.CliTerminalMode).String() + ` ` + i18n.T(i18n.CliTerminalModeEnvVar)) - -fmt.Println(styles.CobraSectionColor().SetString(` -Curently used variables:`).String()) - - body := &uxBlock.TableBody{} - guestInfoPart(body) - cmdData.UxBlocks.Table(body) + cmdData.Stdout.PrintLines( + printer.Style(styles.CobraSectionColor(), i18n.T(i18n.GlobalEnvVariables)), + printer.Style(styles.CobraItemNameColor(), constants.CliLogFilePathEnvVar)+"\t"+i18n.T(i18n.CliLogFilePathEnvVar), + printer.Style(styles.CobraItemNameColor(), constants.CliDataFilePathEnvVar)+"\t"+i18n.T(i18n.CliDataFilePathEnvVar), + printer.Style(styles.CobraItemNameColor(), constants.CliTerminalMode)+"\t"+i18n.T(i18n.CliTerminalModeEnvVar), + printer.EmptyLine, + printer.Style(styles.CobraSectionColor(), i18n.T(i18n.CurrentlyUsedEnvVariables)), + ) + + body := uxBlock.NewTableBody() + guestInfoPart(body) + cmdData.UxBlocks.Table(body) return nil }) -} \ No newline at end of file +} + +func guestInfoPart(tableBody *uxBlock.TableBody) { + cliDataFilePath, _, err := constants.CliDataFilePath() + if err != nil { + cliDataFilePath = err.Error() + } + tableBody.AddStringsRow(i18n.T(i18n.StatusInfoCliDataFilePath), cliDataFilePath) + + logFilePath, _, err := constants.LogFilePath() + if err != nil { + logFilePath = err.Error() + } + tableBody.AddStringsRow(i18n.T(i18n.StatusInfoLogFilePath), logFilePath) + + wgConfigFilePath, _, err := constants.WgConfigFilePath() + if err != nil { + wgConfigFilePath = err.Error() + } + tableBody.AddStringsRow(i18n.T(i18n.StatusInfoWgConfigFilePath), wgConfigFilePath) +} diff --git a/src/cmd/logout.go b/src/cmd/logout.go index d8759f95..9a2a0450 100644 --- a/src/cmd/logout.go +++ b/src/cmd/logout.go @@ -4,39 +4,34 @@ import ( "context" "github.com/zeropsio/zcli/src/cmdBuilder" - "github.com/zeropsio/zcli/src/cliStorage" "github.com/zeropsio/zcli/src/i18n" - "github.com/zeropsio/zcli/src/uxBlock/styles" + "github.com/zeropsio/zcli/src/uxBlock/styles" ) -var logout string - func logoutCmd() *cmdBuilder.Cmd { return cmdBuilder.NewCmd(). Use("logout"). Short(i18n.T(i18n.CmdDescLogout)). HelpFlag(i18n.T(i18n.CmdHelpLogout)). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { - - uxBlocks := cmdData.UxBlocks - - _, err := cmdData.RestApiClient.PostAuthLogout(ctx) - if err != nil { - return err - } - - _, err = cmdData.CliStorage.Update(func(data cliStorage.Data) cliStorage.Data { - return cliStorage.Data{} - }) - if err != nil { - return err - } - - uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.LogoutVpnDisconnecting))) - disconnectVpn(ctx, cmdData.UxBlocks) // TODO: ask - there is no need for any declaration - i can just call this function from anywhere? - uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.LogoutSuccess))) - - return nil - - }) -} \ No newline at end of file + uxBlocks := cmdData.UxBlocks + + _, err := cmdData.RestApiClient.PostAuthLogout(ctx) + if err != nil { + return err + } + + _, err = cmdData.CliStorage.Clear() + if err != nil { + return err + } + + uxBlocks.PrintInfo(styles.InfoLine(i18n.T(i18n.LogoutVpnDisconnecting))) + if isVpnUp(ctx, uxBlocks, 1) { + _ = disconnectVpn(ctx, uxBlocks) + } + uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.LogoutSuccess))) + + return nil + }) +} diff --git a/src/cmd/root.go b/src/cmd/root.go index c84696ff..0b8bb536 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -6,17 +6,16 @@ import ( "github.com/zeropsio/zcli/src/cmd/scope" "github.com/zeropsio/zcli/src/cmdBuilder" - "github.com/zeropsio/zcli/src/constants" "github.com/zeropsio/zcli/src/entity/repository" "github.com/zeropsio/zcli/src/errorsx" "github.com/zeropsio/zcli/src/i18n" - "github.com/zeropsio/zcli/src/uxBlock" + "github.com/zeropsio/zcli/src/printer" "github.com/zeropsio/zcli/src/uxBlock/styles" "github.com/zeropsio/zerops-go/errorCode" ) -func ExecuteCmd() error { - return cmdBuilder.ExecuteRootCmd(rootCmd()) +func ExecuteCmd() { + cmdBuilder.ExecuteRootCmd(rootCmd()) } func rootCmd() *cmdBuilder.Cmd { @@ -34,21 +33,12 @@ func rootCmd() *cmdBuilder.Cmd { AddChildrenCmd(statusShowDebugLogsCmd()). AddChildrenCmd(servicePushCmd()). AddChildrenCmd(envCmd()). - AddChildrenCmd(supportCmd()). + AddChildrenCmd(supportCmd()). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { - fmt.Println(`Welcome to zCli by Zerops! - -To unlock the full potential of zCLI, you need to log in using your Zerops account. -Logging in enables you to access various features and interact with Zerops services seamlessly. - -To log in, simply use the following command: zcli login -Replace with the authentication token generated from your Zerops account. -Once logged in, you'll be able to manage projects, deploy applications, configure VPN, -and much more directly from the command line interface. - -If you encounter any issues during the login process or have any questions, -feel free to find out how to contact our support team by running 'zcli support'. -`) + cmdData.Stdout.PrintLines( + i18n.T(i18n.GuestWelcome), + printer.EmptyLine, + ) // print the default command help cmdData.PrintHelp() @@ -56,7 +46,6 @@ feel free to find out how to contact our support team by running 'zcli support'. return nil }). LoggedUserRunFunc(func(ctx context.Context, cmdData *cmdBuilder.LoggedUserCmdData) error { - var loggedUser string if info, err := cmdData.RestApiClient.GetUserInfo(ctx); err != nil { loggedUser = err.Error() @@ -68,8 +57,7 @@ feel free to find out how to contact our support team by running 'zcli support'. } } - - // TODO: krls - check whole block + // TODO: krls - check whole block if cmdData.CliStorage.Data().ScopeProjectId.Filled() { // project scope is set projectId, _ := cmdData.CliStorage.Data().ScopeProjectId.Get() @@ -81,21 +69,24 @@ feel free to find out how to contact our support team by running 'zcli support'. return err } } else { - fmt.Print(i18n.T(i18n.ScopedProject), err.Error()) + cmdData.Stderr.PrintLines(i18n.T(i18n.ScopedProject), err.Error()) } } else { - fmt.Print(i18n.T(i18n.ScopedProject), fmt.Sprintf("%s [%s]", project.Name.String(), project.ID.Native())) + cmdData.Stdout.PrintLines(i18n.T(i18n.ScopedProject), fmt.Sprintf("%s [%s]", project.Name.String(), project.ID.Native())) } } - var vpnStatusText string + var vpnStatusText string if isVpnUp(ctx, cmdData.UxBlocks, 1) { vpnStatusText = i18n.T(i18n.VpnCheckingConnectionIsActive) } else { vpnStatusText = i18n.T(i18n.VpnCheckingConnectionIsNotActive) } - fmt.Printf("Welcome in Zerops!\nYou are loged as %s \nand your %s.\n\n", loggedUser, vpnStatusText) + cmdData.Stdout.PrintLines( + i18n.T(i18n.LoggedWelcome, loggedUser, vpnStatusText), + printer.EmptyLine, + ) // print the default command help cmdData.PrintHelp() @@ -104,26 +95,6 @@ feel free to find out how to contact our support team by running 'zcli support'. }) } -func guestInfoPart(tableBody *uxBlock.TableBody) { - cliDataFilePath, _, err := constants.CliDataFilePath() - if err != nil { - cliDataFilePath = err.Error() - } - tableBody.AddStringsRow(i18n.T(i18n.StatusInfoCliDataFilePath), cliDataFilePath) - - logFilePath, _, err := constants.LogFilePath() - if err != nil { - logFilePath = err.Error() - } - tableBody.AddStringsRow(i18n.T(i18n.StatusInfoLogFilePath), logFilePath) - - wgConfigFilePath, _, err := constants.WgConfigFilePath() - if err != nil { - wgConfigFilePath = err.Error() - } - tableBody.AddStringsRow(i18n.T(i18n.StatusInfoWgConfigFilePath), wgConfigFilePath) -} - func getRootTemplate() string { return styles.CobraSectionColor().SetString("Usage:").String() + `{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} diff --git a/src/cmd/support.go b/src/cmd/support.go index a872c333..7f583567 100644 --- a/src/cmd/support.go +++ b/src/cmd/support.go @@ -2,32 +2,26 @@ package cmd import ( "context" - "fmt" "github.com/zeropsio/zcli/src/cmdBuilder" "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/printer" + "github.com/zeropsio/zcli/src/uxBlock/styles" ) -var support string - func supportCmd() *cmdBuilder.Cmd { return cmdBuilder.NewCmd(). Use("support"). Short(i18n.T(i18n.CmdDescSupport)). HelpFlag(i18n.T(i18n.CmdHelpSupport)). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { - - - fmt.Println("You can contact Zerops support via:") - fmt.Println("- E-mail: team@zerops.io") - fmt.Println("- Discord: https://discord.com/invite/WDvCZ54") - fmt.Println(` -Additionally, you can explore our documentation -at https://docs.zerops.io/references/cli for further details. - `) - return nil - + cmdData.Stdout.PrintLines( + printer.Style(styles.CobraSectionColor(), i18n.T(i18n.Contact)), + printer.Style(styles.CobraItemNameColor(), "- E-mail")+": team@zerops.io", + printer.Style(styles.CobraItemNameColor(), "- Discord")+": https://discord.com/invite/WDvCZ54", + printer.EmptyLine, + i18n.T(i18n.Documentation), + ) return nil }) } - diff --git a/src/cmd/version.go b/src/cmd/version.go index 32f64d62..9df65068 100644 --- a/src/cmd/version.go +++ b/src/cmd/version.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "fmt" "runtime" "github.com/zeropsio/zcli/src/cmdBuilder" @@ -17,7 +16,7 @@ func versionCmd() *cmdBuilder.Cmd { Short(i18n.T(i18n.CmdDescVersion)). HelpFlag(i18n.T(i18n.CmdHelpVersion)). GuestRunFunc(func(ctx context.Context, cmdData *cmdBuilder.GuestCmdData) error { - fmt.Printf("zcli version %s (%s) %s/%s\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) + cmdData.Stdout.Printf("zcli version %s (%s) %s/%s\n", version, runtime.Version(), runtime.GOOS, runtime.GOARCH) return nil }) diff --git a/src/cmd/vpnUp.go b/src/cmd/vpnUp.go index bdb99575..a1e2856d 100644 --- a/src/cmd/vpnUp.go +++ b/src/cmd/vpnUp.go @@ -132,7 +132,6 @@ func vpnUpCmd() *cmdBuilder.Cmd { return err } - // wait for the vpn to be up if isVpnUp(ctx, uxBlocks, 6) { uxBlocks.PrintInfo(styles.SuccessLine(i18n.T(i18n.VpnUp))) @@ -140,7 +139,6 @@ func vpnUpCmd() *cmdBuilder.Cmd { uxBlocks.PrintWarning(styles.WarningLine(i18n.T(i18n.VpnPingFailed))) } - return nil }) } diff --git a/src/cmdBuilder/createRunFunc.go b/src/cmdBuilder/createRunFunc.go index 0a591c7e..486839e6 100644 --- a/src/cmdBuilder/createRunFunc.go +++ b/src/cmdBuilder/createRunFunc.go @@ -2,6 +2,7 @@ package cmdBuilder import ( "fmt" + "os" "slices" "github.com/pkg/errors" @@ -10,6 +11,7 @@ import ( "github.com/zeropsio/zcli/src/entity" "github.com/zeropsio/zcli/src/flagParams" "github.com/zeropsio/zcli/src/i18n" + "github.com/zeropsio/zcli/src/printer" "github.com/zeropsio/zcli/src/uxBlock" "github.com/zeropsio/zcli/src/zeropsRestApiClient" "github.com/zeropsio/zerops-go/types/uuid" @@ -50,6 +52,8 @@ type GuestCmdData struct { UxBlocks uxBlock.UxBlocks Args map[string][]string Params ParamsReader + Stdout printer.Printer + Stderr printer.Printer PrintHelp func() } @@ -88,6 +92,8 @@ func createCmdRunFunc( UxBlocks: uxBlocks, Args: argsMap, Params: newCmdParamReader(cobraCmd, flagParams), + Stdout: printer.NewPrinter(os.Stdout), + Stderr: printer.NewPrinter(os.Stderr), PrintHelp: func() { cobraCmd.HelpFunc()(cobraCmd, []string{}) diff --git a/src/cmdBuilder/executeRootCmd.go b/src/cmdBuilder/executeRootCmd.go index c53ad08d..6bfb6b41 100644 --- a/src/cmdBuilder/executeRootCmd.go +++ b/src/cmdBuilder/executeRootCmd.go @@ -7,8 +7,8 @@ import ( "os/signal" "syscall" - "github.com/mattn/go-isatty" "github.com/pkg/errors" + "github.com/zeropsio/zcli/src/terminal" "golang.org/x/term" "gopkg.in/yaml.v3" @@ -16,7 +16,6 @@ import ( "github.com/zeropsio/zcli/src/constants" "github.com/zeropsio/zcli/src/errorsx" "github.com/zeropsio/zcli/src/flagParams" - "github.com/zeropsio/zcli/src/i18n" "github.com/zeropsio/zcli/src/logger" "github.com/zeropsio/zcli/src/storage" "github.com/zeropsio/zcli/src/support" @@ -25,12 +24,12 @@ import ( "github.com/zeropsio/zerops-go/apiError" ) -func ExecuteRootCmd(rootCmd *Cmd) (err error) { +func ExecuteRootCmd(rootCmd *Cmd) { ctx, cancel := context.WithCancel(context.Background()) regSignals(cancel) ctx = support.Context(ctx) - isTerminal := isTerminal() + isTerminal := terminal.IsTerminal() width, _, err := term.GetSize(0) if err != nil { @@ -41,33 +40,28 @@ func ExecuteRootCmd(rootCmd *Cmd) (err error) { uxBlocks := uxBlock.NewBlock(outputLogger, debugFileLogger, isTerminal, width, cancel) - defer func() { - if err != nil { - printError(err, uxBlocks) - } - }() - cliStorage, err := createCliStorage() if err != nil { - return err + printError(err, uxBlocks) } flagParams := flagParams.New() cobraCmd, err := buildCobraCmd(rootCmd, flagParams, uxBlocks, cliStorage) if err != nil { - return err + printError(err, uxBlocks) } err = cobraCmd.ExecuteContext(ctx) if err != nil { printError(err, uxBlocks) } - - return nil } func printError(err error, uxBlocks uxBlock.UxBlocks) { + if err == nil { + return + } uxBlocks.LogDebug(fmt.Sprintf("error: %+v", err)) if userErr := errorsx.AsUserError(err); userErr != nil { @@ -90,31 +84,7 @@ func printError(err error, uxBlocks uxBlock.UxBlocks) { } uxBlocks.PrintError(styles.ErrorLine(err.Error())) -} - -type terminalMode string - -const ( - TerminalModeAuto terminalMode = "auto" - TerminalModeDisabled terminalMode = "disabled" - TerminalModeEnabled terminalMode = "enabled" -) - -func isTerminal() bool { - env := os.Getenv(constants.CliTerminalMode) - - switch terminalMode(env) { - case TerminalModeAuto, "": - return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) - case TerminalModeDisabled: - return false - case TerminalModeEnabled: - return true - default: - os.Stdout.WriteString(styles.WarningLine(i18n.T(i18n.UnknownTerminalMode, env)).String()) - - return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) - } + os.Exit(1) } func createLoggers(isTerminal bool) (*logger.Handler, *logger.Handler) { diff --git a/src/i18n/en.go b/src/i18n/en.go index fd90f67d..597d43d8 100644 --- a/src/i18n/en.go +++ b/src/i18n/en.go @@ -3,6 +3,27 @@ package i18n import "fmt" var en = map[string]string{ + // root + GuestWelcome: `Welcome to zCli by Zerops! + +To unlock the full potential of zCLI, you need to log in using your Zerops account. +Logging in enables you to access various features and interact with Zerops services seamlessly. + +To log in, simply use the following command: zcli login +Replace with the authentication token generated from your Zerops account. +Once logged in, you'll be able to manage projects, deploy applications, configure VPN, +and much more directly from the command line interface. + +If you encounter any issues during the login process or have any questions, +feel free to find out how to contact our support team by running 'zcli support'.`, + LoggedWelcome: `Welcome in Zerops! +You are loged as %s +and your %s.`, + + // env + GlobalEnvVariables: "Global Env Variables:", + CurrentlyUsedEnvVariables: "Curently used variables:", + // login CmdHelpLogin: "the login command.", CmdDescLogin: "Login into Zerops with generated Zerops token", @@ -11,10 +32,10 @@ var en = map[string]string{ RegionTableColumnName: "Name", // logout - CmdHelpLogout: "the logout command.", - CmdDescLogout: "Disconnect from VPN and log out from your Zerops account", - LogoutVpnDisconnecting: "Disconnecting from VPN. Please provide your password if prompted.", - LogoutSuccess: "Successfully logged out. You are now disconnected from Zerops services.", + CmdHelpLogout: "the logout command.", + CmdDescLogout: "Disconnect from VPN and log out from your Zerops account", + LogoutVpnDisconnecting: "Disconnecting from VPN. Please provide your password if prompted.", + LogoutSuccess: "Successfully logged out. You are now disconnected from Zerops services.", // scope CmdHelpScope: "the scope command.", @@ -169,9 +190,12 @@ var en = map[string]string{ // support CmdHelpSupport: "the support command.", CmdDescSupport: "How to contact Zerops support for assistance", + Contact: "You can contact Zerops support via:", + Documentation: `Additionally, you can explore our documentation +at https://docs.zerops.io/references/cli for further details.`, // env - CmdHelpEnv: "the support command.", + CmdHelpEnv: "the env command.", CmdDescEnv: "Displays global environment variables, their paths and additional options", // vpn @@ -286,7 +310,7 @@ var en = map[string]string{ UnauthenticatedUser: `unauthenticated user, login before proceeding with this command zcli login {token} -more info: https://docs.zerops.io/documentation/cli/authorization.html`, +more info: https://docs.zerops.io/references/cli/`, // scope SelectedProject: "Selected project", diff --git a/src/i18n/i18n.go b/src/i18n/i18n.go index 80c2a3a0..774bedd6 100644 --- a/src/i18n/i18n.go +++ b/src/i18n/i18n.go @@ -16,6 +16,14 @@ func T(textConst string, args ...interface{}) string { const CustomerSupportLink = "https://support.zerops.io/" const ( + // root + GuestWelcome = "GuestWelcome" + LoggedWelcome = "LoggedWelcome" + + // env + GlobalEnvVariables = "GlobalEnvVariables" + CurrentlyUsedEnvVariables = "CurrentlyUsedEnvVariables" + // login CmdHelpLogin = "CmdHelpLogin" CmdDescLogin = "CmdDescLogin" @@ -23,7 +31,7 @@ const ( RegionNotFound = "RegionNotFound" RegionTableColumnName = "RegionTableColumnName" - // logout + // logout CmdHelpLogout = "CmdHelpLogout" CmdDescLogout = "CmdDescLogout" LogoutVpnDisconnecting = "LogoutVpnDisconnecting" @@ -167,8 +175,10 @@ const ( CmdDescSupport = "CmdDescSupport" // support - CmdHelpEnv = "CmdHelpEnv" - CmdDescEnv = "CmdDescEnv" + CmdHelpEnv = "CmdHelpEnv" + CmdDescEnv = "CmdDescEnv" + Contact = "Contact" + Documentation = "Documentation" // vpn CmdHelpVpn = "CmdHelpVpn" diff --git a/src/logger/handler.go b/src/logger/handler.go index e02d5729..437cc39f 100644 --- a/src/logger/handler.go +++ b/src/logger/handler.go @@ -25,7 +25,7 @@ func NewOutputLogger(config OutputConfig) *Handler { formatter = &logrus.TextFormatter{DisableColors: true} } - l.AddHook(&StdoutHook{ + l.AddHook(&TerminalHook{ levels: []logrus.Level{logrus.DebugLevel, logrus.InfoLevel, logrus.WarnLevel, logrus.ErrorLevel}, formatter: formatter, }) diff --git a/src/logger/hooks.go b/src/logger/hooks.go index a95e4841..00bc074b 100644 --- a/src/logger/hooks.go +++ b/src/logger/hooks.go @@ -1,6 +1,7 @@ package logger import ( + "bytes" "fmt" "io" "os" @@ -51,17 +52,22 @@ func (hook *VarLogHook) Fire(entry *logrus.Entry) error { return nil } -type StdoutHook struct { - levels []logrus.Level - formatter logrus.Formatter +type TerminalHook struct { + levels []logrus.Level + formatter logrus.Formatter + isTerminal bool } -func (hook *StdoutHook) Levels() []logrus.Level { +func (hook *TerminalHook) Levels() []logrus.Level { return hook.levels } -func (hook *StdoutHook) Fire(entry *logrus.Entry) error { +func (hook *TerminalHook) Fire(entry *logrus.Entry) error { msg := []byte(entry.Message) + if !hook.isTerminal { + msg = bytes.ReplaceAll(msg, []byte{'\n'}, []byte{' '}) + entry.Message = string(msg) + } if hook.formatter != nil { if formattedEntry, err := hook.formatter.Format(entry); err != nil { fmt.Fprintf(os.Stderr, "Unable to read entry, %v", err) @@ -71,6 +77,7 @@ func (hook *StdoutHook) Fire(entry *logrus.Entry) error { } else { msg = append(msg, '\n') } + if entry.Level <= logrus.ErrorLevel { os.Stderr.Write(msg) } else { diff --git a/src/printer/printer.go b/src/printer/printer.go new file mode 100644 index 00000000..8a0108fb --- /dev/null +++ b/src/printer/printer.go @@ -0,0 +1,48 @@ +package printer + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/lipgloss" + "github.com/zeropsio/zcli/src/terminal" +) + +const ( + NewLine = "\n" + EmptyLine = "" +) + +type Printer struct { + out io.Writer +} + +func NewPrinter(out io.Writer) Printer { + return Printer{ + out: out, + } +} + +func (p Printer) Printf(format string, args ...any) { + fmt.Fprintf(p.out, format, args...) +} + +func (p Printer) Print(a ...any) { + fmt.Fprint(p.out, a...) +} + +func (p Printer) Println(a ...any) { + fmt.Fprintln(p.out, a...) +} + +func (p Printer) PrintLines(lines ...string) { + p.Println(strings.Join(lines, NewLine)) +} + +func Style(s lipgloss.Style, text string) string { + if !terminal.IsTerminal() { + return s.Value() + } + return s.Render(text) +} diff --git a/src/storage/handler.go b/src/storage/handler.go index 2110ceac..e786b634 100644 --- a/src/storage/handler.go +++ b/src/storage/handler.go @@ -67,6 +67,13 @@ func (h *Handler[T]) Update(callback func(T) T) (T, error) { return h.data, h.save(h.data) } +func (h *Handler[T]) Clear() (T, error) { + h.lock.Lock() + defer h.lock.Unlock() + var t T + return h.data, h.save(t) +} + func (h *Handler[T]) save(data T) error { h.data = data diff --git a/src/terminal/terminal.go b/src/terminal/terminal.go new file mode 100644 index 00000000..c1c2e44c --- /dev/null +++ b/src/terminal/terminal.go @@ -0,0 +1,43 @@ +package terminal + +import ( + "os" + + "github.com/mattn/go-isatty" + "github.com/zeropsio/zcli/src/constants" +) + +type terminalMode string + +const ( + ModeAuto terminalMode = "auto" + ModeDisabled terminalMode = "disabled" + ModeEnabled terminalMode = "enabled" +) + +func isTerminal() bool { + env := os.Getenv(constants.CliTerminalMode) + + switch terminalMode(env) { + case ModeAuto, "": + return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) + case ModeDisabled: + return false + case ModeEnabled: + return true + default: + + return isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd()) + } +} + +var _isTerminal *bool + +func IsTerminal() bool { + if _isTerminal != nil { + return *_isTerminal + } + _isTerminal = new(bool) + *_isTerminal = isTerminal() + return *_isTerminal +} diff --git a/src/uxBlock/styles/line.go b/src/uxBlock/styles/line.go index 48836396..41ad95de 100644 --- a/src/uxBlock/styles/line.go +++ b/src/uxBlock/styles/line.go @@ -2,8 +2,10 @@ package styles import ( "fmt" + "slices" "github.com/charmbracelet/lipgloss" + "github.com/zeropsio/zcli/src/terminal" ) type Line struct { @@ -35,11 +37,11 @@ func (l Line) NotEmpty() bool { } func (l Line) String() string { - args := l.args + args := slices.Clone(l.args) for i, arg := range args { if typed, ok := arg.(lipgloss.Style); ok { - if l.styles { + if l.styles && terminal.IsTerminal() { args[i] = typed.String() } else { args[i] = typed.Value() diff --git a/src/uxBlock/styles/styles.go b/src/uxBlock/styles/styles.go index c33de521..722ead6d 100644 --- a/src/uxBlock/styles/styles.go +++ b/src/uxBlock/styles/styles.go @@ -7,40 +7,39 @@ import ( ) const ( - // SYMBOLS + // SYMBOLS SuccessIcon = "✔" ErrorIcon = "✗" SelectIcon = "➔" InfoIcon = "➤" WarningIcon = "!" - // COLORS - prefixTextColorLight = "15" - prefixTextColorDark = "16" + // COLORS + prefixTextColorLight = "15" + prefixTextColorDark = "16" - successColorLight = "28" - successColorDark = "10" + successColorLight = "28" + successColorDark = "10" - warningColorLight = "142" - warningColorDark = "11" + warningColorLight = "142" + warningColorDark = "11" - selectColorLight = "33" - selectColorDark = "45" + selectColorLight = "33" + selectColorDark = "45" - infoColorLight = "16" - infoColorDark = "15" + infoColorLight = "16" + infoColorDark = "15" - errorColorLight = "196" - errorColorDark = "196" + errorColorLight = "196" + errorColorDark = "196" - cobraSectionColorLight = "4" - cobraSectionColorDark = "139" + cobraSectionColorLight = "4" + cobraSectionColorDark = "139" - cobraItemColorLight = "5" - cobraItemColorDark = "51" + cobraItemColorLight = "5" + cobraItemColorDark = "51" ) - var defaultRender = lipgloss.NewRenderer(os.Stdout) func defaultStyle() lipgloss.Style { @@ -75,21 +74,21 @@ func ErrorColor() lipgloss.Style { func WarningPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). - Background(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}). PaddingLeft(1).PaddingRight(1). SetString("WARN") } func WarningColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}) + Foreground(lipgloss.AdaptiveColor{Light: warningColorLight, Dark: warningColorDark}) } func InfoPrefix() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). - Background(lipgloss.AdaptiveColor{Light: infoColorLight, Dark: infoColorDark}). + Foreground(lipgloss.AdaptiveColor{Light: prefixTextColorLight, Dark: prefixTextColorDark}). + Background(lipgloss.AdaptiveColor{Light: infoColorLight, Dark: infoColorDark}). PaddingLeft(1).PaddingRight(1). SetString("INFO") } @@ -109,20 +108,20 @@ func SelectPrefix() lipgloss.Style { func SelectColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: selectColorLight, Dark: selectColorDark}) + Foreground(lipgloss.AdaptiveColor{Light: selectColorLight, Dark: selectColorDark}) } func CobraSectionColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}). - BorderBottom(true). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}) + Foreground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}). + BorderBottom(true). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.AdaptiveColor{Light: cobraSectionColorLight, Dark: cobraSectionColorDark}) } func CobraItemNameColor() lipgloss.Style { return defaultStyle(). - Foreground(lipgloss.AdaptiveColor{Light: cobraItemColorLight, Dark: cobraItemColorDark}) + Foreground(lipgloss.AdaptiveColor{Light: cobraItemColorLight, Dark: cobraItemColorDark}) } func DialogBox() lipgloss.Style {