diff --git a/cli/common/flags.go b/cli/common/flags.go index e00b23a45..665b64578 100644 --- a/cli/common/flags.go +++ b/cli/common/flags.go @@ -21,6 +21,12 @@ import ( ) var GlobalFlags = append([]cli.Flag{ + &cli.StringFlag{ + EnvVars: []string{"WOODPECKER_CONFIG"}, + Name: "config", + Aliases: []string{"c"}, + Usage: "path to config file", + }, &cli.StringFlag{ EnvVars: []string{"WOODPECKER_TOKEN"}, Name: "token", diff --git a/cli/common/hooks.go b/cli/common/hooks.go index 82a8f9d20..5ff2a1242 100644 --- a/cli/common/hooks.go +++ b/cli/common/hooks.go @@ -8,6 +8,7 @@ import ( "github.com/rs/zerolog/log" "github.com/urfave/cli/v2" + "go.woodpecker-ci.org/woodpecker/v2/cli/internal/config" "go.woodpecker-ci.org/woodpecker/v2/cli/update" ) @@ -17,7 +18,7 @@ var ( ) func Before(c *cli.Context) error { - if err := SetupGlobalLogger(c); err != nil { + if err := setupGlobalLogger(c); err != nil { return err } @@ -49,7 +50,7 @@ func Before(c *cli.Context) error { } }() - return nil + return config.Load(c) } func After(_ *cli.Context) error { diff --git a/cli/common/zerologger.go b/cli/common/zerologger.go index 2d0e2acea..d3e275898 100644 --- a/cli/common/zerologger.go +++ b/cli/common/zerologger.go @@ -20,6 +20,6 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/shared/logger" ) -func SetupGlobalLogger(c *cli.Context) error { +func setupGlobalLogger(c *cli.Context) error { return logger.SetupGlobalLogger(c, false) } diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go new file mode 100644 index 000000000..32a047250 --- /dev/null +++ b/cli/internal/config/config.go @@ -0,0 +1,131 @@ +package config + +import ( + "encoding/json" + "errors" + "os" + + "github.com/adrg/xdg" + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" + "github.com/zalando/go-keyring" +) + +type Config struct { + ServerURL string `json:"server_url"` + Token string `json:"-"` + LogLevel string `json:"log_level"` +} + +func Load(c *cli.Context) error { + // If the command is setup, we don't need to load the config + if firstArg := c.Args().First(); firstArg == "setup" { + return nil + } + + config, err := Get(c, c.String("config")) + if err != nil { + return err + } + + if config == nil && !c.IsSet("server-url") && !c.IsSet("token") { + log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup`") + return errors.New("woodpecker-cli is not setup") + } + + if !c.IsSet("server") { + err = c.Set("server", config.ServerURL) + if err != nil { + return err + } + } + + if !c.IsSet("token") { + err = c.Set("token", config.Token) + if err != nil { + return err + } + } + + if !c.IsSet("log-level") { + err = c.Set("log-level", config.LogLevel) + if err != nil { + return err + } + } + + return nil +} + +func getConfigPath(configPath string) (string, error) { + if configPath != "" { + return configPath, nil + } + + configPath, err := xdg.ConfigFile("woodpecker/config.json") + if err != nil { + return "", err + } + + return configPath, nil +} + +func Get(ctx *cli.Context, _configPath string) (*Config, error) { + configPath, err := getConfigPath(_configPath) + if err != nil { + return nil, err + } + + content, err := os.ReadFile(configPath) + if err != nil && !os.IsNotExist(err) { + log.Debug().Err(err).Msg("Failed to read the config file") + return nil, err + } else if err != nil && os.IsNotExist(err) { + log.Debug().Msg("The config file does not exist") + return nil, nil + } + + c := &Config{} + err = json.Unmarshal(content, c) + if err != nil { + return nil, err + } + + // load token from keyring + service := ctx.App.Name + secret, err := keyring.Get(service, c.ServerURL) + if err != nil && !errors.Is(err, keyring.ErrNotFound) { + return nil, err + } + if err == nil { + c.Token = secret + } + + return c, nil +} + +func Save(ctx *cli.Context, _configPath string, c *Config) error { + config, err := json.Marshal(c) + if err != nil { + return err + } + + configPath, err := getConfigPath(_configPath) + if err != nil { + return err + } + + // save token to keyring + service := ctx.App.Name + err = keyring.Set(service, c.ServerURL, c.Token) + if err != nil { + return err + } + + err = os.WriteFile(configPath, config, 0o600) + if err != nil { + return err + } + + return nil +} diff --git a/cli/setup/setup.go b/cli/setup/setup.go new file mode 100644 index 000000000..b114cf750 --- /dev/null +++ b/cli/setup/setup.go @@ -0,0 +1,88 @@ +package setup + +import ( + "errors" + "strings" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v2" + + "go.woodpecker-ci.org/woodpecker/v2/cli/internal/config" + "go.woodpecker-ci.org/woodpecker/v2/cli/setup/ui" +) + +// Command exports the setup command. +var Command = &cli.Command{ + Name: "setup", + Usage: "setup the woodpecker-cli for the first time", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "server-url", + Usage: "The URL of the woodpecker server", + }, + &cli.StringFlag{ + Name: "token", + Usage: "The token to authenticate with the woodpecker server", + }, + }, + Action: setup, +} + +func setup(c *cli.Context) error { + _config, err := config.Get(c, c.String("config")) + if err != nil { + return err + } else if _config != nil { + setupAgain, err := ui.Confirm("The woodpecker-cli was already configured. Do you want to configure it again?") + if err != nil { + return err + } + + if !setupAgain { + log.Info().Msg("Configuration skipped") + return nil + } + } + + serverURL := c.String("server-url") + + if serverURL == "" { + serverURL, err = ui.Ask("Enter the URL of the woodpecker server", "https://ci.woodpecker-ci.org", true) + if err != nil { + return err + } + + if serverURL == "" { + return errors.New("server URL cannot be empty") + } + } + + if !strings.Contains(serverURL, "://") { + serverURL = "https://" + serverURL + } + + token := c.String("token") + if token == "" { + token, err = receiveTokenFromUI(c.Context, serverURL) + if err != nil { + return err + } + + if token == "" { + return errors.New("no token received from the UI") + } + } + + err = config.Save(c, c.String("config"), &config.Config{ + ServerURL: serverURL, + Token: token, + LogLevel: "info", + }) + if err != nil { + return err + } + + log.Info().Msg("The woodpecker-cli has been successfully setup") + + return nil +} diff --git a/cli/setup/token_fetcher.go b/cli/setup/token_fetcher.go new file mode 100644 index 000000000..54dd18eee --- /dev/null +++ b/cli/setup/token_fetcher.go @@ -0,0 +1,117 @@ +package setup + +import ( + "context" + "errors" + "fmt" + "math/rand" + "net/http" + "os/exec" + "runtime" + "time" + + "github.com/gin-gonic/gin" + "github.com/rs/zerolog/log" +) + +func receiveTokenFromUI(c context.Context, serverURL string) (string, error) { + port := randomPort() + + tokenReceived := make(chan string) + + srv := &http.Server{Addr: fmt.Sprintf("127.0.0.1:%d", port)} + srv.Handler = setupRouter(tokenReceived) + + go func() { + log.Debug().Msgf("Listening for token response on :%d", port) + _ = srv.ListenAndServe() + }() + + defer func() { + log.Debug().Msg("Shutting down server") + _ = srv.Shutdown(c) + }() + + err := openBrowser(fmt.Sprintf("%s/cli/auth?port=%d", serverURL, port)) + if err != nil { + return "", err + } + + // wait for token to be received or timeout + select { + case token := <-tokenReceived: + return token, nil + case <-c.Done(): + return "", c.Err() + case <-time.After(5 * time.Minute): + return "", errors.New("timed out waiting for token") + } +} + +func setupRouter(tokenReceived chan string) *gin.Engine { + gin.SetMode(gin.ReleaseMode) + e := gin.New() + e.UseRawPath = true + e.Use(gin.Recovery()) + + e.Use(func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + }) + + e.POST("/token", func(c *gin.Context) { + data := struct { + Token string `json:"token"` + }{} + + err := c.BindJSON(&data) + if err != nil { + log.Debug().Err(err).Msg("Failed to bind JSON") + c.JSON(400, gin.H{ + "error": "invalid request", + }) + return + } + + tokenReceived <- data.Token + + c.JSON(200, gin.H{ + "ok": "true", + }) + }) + + return e +} + +func openBrowser(url string) error { + var err error + + log.Debug().Msgf("Opening browser with URL: %s", url) + + switch runtime.GOOS { + case "linux": + err = exec.Command("xdg-open", url).Start() + case "windows": + err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() + case "darwin": + err = exec.Command("open", url).Start() + default: + err = fmt.Errorf("unsupported platform") + } + return err +} + +func randomPort() int { + s1 := rand.NewSource(time.Now().UnixNano()) + r1 := rand.New(s1) + return r1.Intn(10000) + 20000 +} diff --git a/cli/setup/ui/ask.go b/cli/setup/ui/ask.go new file mode 100644 index 000000000..2fa8c539e --- /dev/null +++ b/cli/setup/ui/ask.go @@ -0,0 +1,79 @@ +package ui + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +type askModel struct { + prompt string + required bool + textInput textinput.Model + err error +} + +func (m askModel) Init() tea.Cmd { + return textinput.Blink +} + +func (m askModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter: + if !m.required || (m.required && strings.TrimSpace(m.textInput.Value()) != "") { + return m, tea.Quit + } + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + default: + return m, cmd + } + + m.textInput, cmd = m.textInput.Update(msg) + return m, cmd +} + +func (m askModel) View() string { + return fmt.Sprintf( + "%s\n\n%s\n\n%s", + m.prompt, + m.textInput.View(), + "(esc to quit)", + ) + "\n" +} + +func Ask(prompt, placeholder string, required bool) (string, error) { + ti := textinput.New() + ti.Placeholder = placeholder + ti.Focus() + ti.CharLimit = 156 + ti.Width = 40 + + p := tea.NewProgram(askModel{ + prompt: prompt, + textInput: ti, + required: required, + err: nil, + }) + + _m, err := p.Run() + if err != nil { + return "", err + } + + m, ok := _m.(askModel) + if !ok { + return "", fmt.Errorf("unexpected model: %T", _m) + } + + text := strings.TrimSpace(m.textInput.Value()) + + return text, nil +} diff --git a/cli/setup/ui/confirm.go b/cli/setup/ui/confirm.go new file mode 100644 index 000000000..350fdb2e4 --- /dev/null +++ b/cli/setup/ui/confirm.go @@ -0,0 +1,71 @@ +package ui + +import ( + "fmt" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" +) + +type confirmModel struct { + confirmed bool + prompt string + err error +} + +func (m confirmModel) Init() tea.Cmd { + return textinput.Blink +} + +func (m confirmModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + if msg.Runes != nil { + switch msg.Runes[0] { + case 'y': + m.confirmed = true + return m, tea.Quit + case 'n': + m.confirmed = false + return m, tea.Quit + } + } + + switch msg.Type { + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + } + default: + return m, nil + } + + return m, cmd +} + +func (m confirmModel) View() string { + return fmt.Sprintf( + "%s y / n (esc to quit)", + m.prompt, + ) + "\n" +} + +func Confirm(prompt string) (bool, error) { + p := tea.NewProgram(confirmModel{ + prompt: prompt, + err: nil, + }) + + _m, err := p.Run() + if err != nil { + return false, err + } + + m, ok := _m.(confirmModel) + if !ok { + return false, fmt.Errorf("unexpected model: %T", _m) + } + + return m.confirmed, nil +} diff --git a/cmd/cli/app.go b/cmd/cli/app.go index 92ba17237..db6edca17 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -29,6 +29,7 @@ import ( "go.woodpecker-ci.org/woodpecker/v2/cli/registry" "go.woodpecker-ci.org/woodpecker/v2/cli/repo" "go.woodpecker-ci.org/woodpecker/v2/cli/secret" + "go.woodpecker-ci.org/woodpecker/v2/cli/setup" "go.woodpecker-ci.org/woodpecker/v2/cli/update" "go.woodpecker-ci.org/woodpecker/v2/cli/user" "go.woodpecker-ci.org/woodpecker/v2/version" @@ -58,6 +59,7 @@ func newApp() *cli.App { lint.Command, loglevel.Command, cron.Command, + setup.Command, update.Command, } diff --git a/go.mod b/go.mod index ecdfa39c2..9476c522b 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,13 @@ require ( codeberg.org/6543/go-yaml2json v1.0.0 codeberg.org/6543/xyaml v1.1.0 github.com/6543/logfile-open v1.2.1 + github.com/adrg/xdg v0.4.0 github.com/alessio/shellescape v1.4.2 github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/caddyserver/certmagic v0.20.0 github.com/cenkalti/backoff/v4 v4.2.1 + github.com/charmbracelet/bubbles v0.18.0 + github.com/charmbracelet/bubbletea v0.25.0 github.com/distribution/reference v0.5.0 github.com/docker/cli v24.0.9+incompatible github.com/docker/docker v24.0.9+incompatible @@ -50,6 +53,7 @@ require ( github.com/urfave/cli/v2 v2.27.1 github.com/xanzy/go-gitlab v0.97.0 github.com/xeipuuv/gojsonschema v1.2.0 + github.com/zalando/go-keyring v0.2.3 go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.19.0 golang.org/x/net v0.21.0 @@ -72,13 +76,17 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.10.2 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect + github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect + github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect @@ -97,6 +105,7 @@ require ( github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect github.com/goccy/go-json v0.10.2 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect @@ -120,6 +129,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/mholt/acmez v1.2.0 // indirect @@ -127,6 +137,9 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect @@ -135,7 +148,7 @@ require ( github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/rivo/uniseg v0.4.4 // indirect + github.com/rivo/uniseg v0.4.6 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect diff --git a/go.sum b/go.sum index a15a4b201..5034e80a4 100644 --- a/go.sum +++ b/go.sum @@ -20,8 +20,12 @@ github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6Xge github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= +github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -38,6 +42,12 @@ github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqy github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= +github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= +github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= +github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= +github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= +github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= @@ -49,6 +59,8 @@ github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4M github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY= +github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -57,6 +69,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= +github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -81,8 +95,9 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/expr-lang/expr v1.16.1 h1:Na8CUcMdyGbnNpShY7kzcHCU7WqxuL+hnxgHZ4vaz/A= @@ -138,6 +153,8 @@ github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGF github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= @@ -299,6 +316,9 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= @@ -325,6 +345,12 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= @@ -364,9 +390,10 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= -github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= +github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -438,6 +465,8 @@ github.com/xrash/smetrics v0.0.0-20231213231151-1d8dd44e695e/go.mod h1:N3UwUGtsr github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zalando/go-keyring v0.2.3 h1:v9CUu9phlABObO4LPWycf+zwMG7nlbb3t/B5wa97yms= +github.com/zalando/go-keyring v0.2.3/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= @@ -537,11 +566,13 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/web/components.d.ts b/web/components.d.ts index 833a12b0d..61ce4d6e8 100644 --- a/web/components.d.ts +++ b/web/components.d.ts @@ -110,6 +110,7 @@ declare module 'vue' { Tabs: typeof import('./src/components/layout/scaffold/Tabs.vue')['default'] TextField: typeof import('./src/components/form/TextField.vue')['default'] UserAPITab: typeof import('./src/components/user/UserAPITab.vue')['default'] + UserCLIAndAPITab: typeof import('./src/components/user/UserCLIAndAPITab.vue')['default'] UserGeneralTab: typeof import('./src/components/user/UserGeneralTab.vue')['default'] UserSecretsTab: typeof import('./src/components/user/UserSecretsTab.vue')['default'] Warning: typeof import('./src/components/atomic/Warning.vue')['default'] diff --git a/web/src/assets/locales/en.json b/web/src/assets/locales/en.json index 8fde513d9..159120e5c 100644 --- a/web/src/assets/locales/en.json +++ b/web/src/assets/locales/en.json @@ -486,15 +486,13 @@ "pr_warning": "Please be careful with this option as a bad actor can submit a malicious pull request that exposes your secrets." } }, - "api": { - "api": "API", - "desc": "Personal Access Token and API usage", + "cli_and_api": { + "cli_and_api": "CLI & API", + "desc": "Personal Access Token, CLI and API usage", "token": "Personal Access Token", - "shell_setup": "Shell setup", "api_usage": "Example API Usage", "cli_usage": "Example CLI Usage", - "dl_cli": "Download CLI", - "shell_setup_before": "do shell setup steps before", + "download_cli": "Download CLI", "reset_token": "Reset token", "swagger_ui": "Swagger UI" } @@ -508,5 +506,12 @@ "running_version": "You are running Woodpecker {0}", "update_woodpecker": "Please update your Woodpecker instance to {0}", "global_level_secret": "global secret", - "org_level_secret": "organization secret" + "org_level_secret": "organization secret", + "login_to_cli": "Login to CLI", + "login_to_cli_description": "By continuing you will be logged in to the CLI.", + "abort": "Abort", + "cli_login_success": "Login to CLI successful", + "cli_login_failed": "Login to CLI failed", + "cli_login_denied": "Login to CLI denied", + "return_to_cli": "You can now close this tab and return to the CLI." } diff --git a/web/src/components/user/UserAPITab.vue b/web/src/components/user/UserCLIAndAPITab.vue similarity index 64% rename from web/src/components/user/UserAPITab.vue rename to web/src/components/user/UserCLIAndAPITab.vue index 2402612df..21d80b9b5 100644 --- a/web/src/components/user/UserAPITab.vue +++ b/web/src/components/user/UserCLIAndAPITab.vue @@ -1,43 +1,38 @@