diff --git a/.gitignore b/.gitignore
index 8ec8f2b..8575996 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,6 @@ config.toml
# build
pb
+
+# OS Files
+.DS_Store
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..8f2c364
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,39 @@
+linters:
+ disable-all: true
+ enable:
+ - typecheck
+ - goimports
+ - misspell
+ - govet
+ - revive
+ - ineffassign
+ - gomodguard
+ - gofmt
+ - unused
+ - gofumpt
+
+linters-settings:
+ golint:
+ min-confidence: 0
+
+ misspell:
+ locale: US
+
+ gofumpt:
+ lang-version: "1.17"
+
+ # Choose whether or not to use the extra rules that are disabled
+ # by default
+ extra-rules: false
+
+issues:
+ exclude-use-default: false
+ exclude:
+ - instead of using struct literal
+ - should have a package comment
+ - should have comment or be unexported
+ - time-naming
+ - error strings should not be capitalized or end with punctuation or a newline
+
+service:
+ golangci-lint-version: 1.43.0 # use the fixed version to not introduce new linters unexpectedly
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..481b82f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,57 @@
+PWD := $(shell pwd)
+GOPATH := $(shell go env GOPATH)
+LDFLAGS := $(shell go run buildscripts/gen-ldflags.go)
+
+GOARCH := $(shell go env GOARCH)
+GOOS := $(shell go env GOOS)
+
+VERSION ?= $(shell git describe --tags)
+TAG ?= "parseablehq/pb:$(VERSION)"
+
+all: build
+
+checks:
+ @echo "Checking dependencies"
+ @(env bash $(PWD)/buildscripts/checkdeps.sh)
+
+getdeps:
+ @mkdir -p ${GOPATH}/bin
+ @echo "Installing golangci-lint" && curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin
+ @echo "Installing stringer" && go install -v golang.org/x/tools/cmd/stringer@latest
+ @echo "Installing staticheck" && go install honnef.co/go/tools/cmd/staticcheck@latest
+
+crosscompile:
+ @(env bash $(PWD)/buildscripts/cross-compile.sh)
+
+verifiers: getdeps vet lint
+
+docker: build
+ @docker build -t $(TAG) . -f Dockerfile.dev
+
+vet:
+ @echo "Running $@"
+ @GO111MODULE=on go vet $(PWD)/...
+
+lint:
+ @echo "Running $@ check"
+ @GO111MODULE=on ${GOPATH}/bin/golangci-lint run --timeout=5m --config ./.golangci.yml
+ @GO111MODULE=on ${GOPATH}/bin/staticcheck -tests=false -checks="all,-ST1000,-ST1003,-ST1016,-ST1020,-ST1021,-ST1022,-ST1023,-ST1005" ./...
+
+# Builds pb locally.
+build: checks
+ @echo "Building pb binary to './pb'"
+ @GO111MODULE=on CGO_ENABLED=0 go build -trimpath -tags kqueue --ldflags "$(LDFLAGS)" -o $(PWD)/pb
+
+# Builds pb and installs it to $GOPATH/bin.
+install: build
+ @echo "Installing pb binary to '$(GOPATH)/bin/pb'"
+ @mkdir -p $(GOPATH)/bin && cp -f $(PWD)/pb $(GOPATH)/bin/pb
+ @echo "Installation successful. To learn more, try \"pb --help\"."
+
+clean:
+ @echo "Cleaning up all the generated files"
+ @find . -name '*.test' | xargs rm -fv
+ @find . -name '*~' | xargs rm -fv
+ @rm -rvf pb
+ @rm -rvf build
+ @rm -rvf release
diff --git a/buildscripts/cross-compile.sh b/buildscripts/cross-compile.sh
new file mode 100644
index 0000000..e7b7e16
--- /dev/null
+++ b/buildscripts/cross-compile.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+#
+# Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+
+set -e
+
+# Enable tracing if set.
+[ -n "$BASH_XTRACEFD" ] && set -x
+
+function _init() {
+ ## All binaries are static make sure to disable CGO.
+ export CGO_ENABLED=0
+
+ ## List of architectures and OS to test coss compilation.
+ SUPPORTED_OSARCH="linux/amd64 linux/arm64 darwin/arm64 darwin/amd64 windows/amd64"
+}
+
+function _build() {
+ local osarch=$1
+ IFS=/ read -r -a arr <<<"$osarch"
+ os="${arr[0]}"
+ arch="${arr[1]}"
+ package=$(go list -f '{{.ImportPath}}')
+ printf -- "--> %15s:%s\n" "${osarch}" "${package}"
+
+ # Go build to build the binary.
+ export GOOS=$os
+ export GOARCH=$arch
+ export GO111MODULE=on
+ export CGO_ENABLED=0
+ go build -tags kqueue -o /dev/null
+}
+
+function main() {
+ echo "Testing builds for OS/Arch: ${SUPPORTED_OSARCH}"
+ for each_osarch in ${SUPPORTED_OSARCH}; do
+ _build "${each_osarch}"
+ done
+}
+
+_init && main "$@"
diff --git a/buildscripts/gen-ldflags.go b/buildscripts/gen-ldflags.go
new file mode 100644
index 0000000..9a412be
--- /dev/null
+++ b/buildscripts/gen-ldflags.go
@@ -0,0 +1,118 @@
+//go:build ignore
+// +build ignore
+
+// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+ "time"
+)
+
+func genLDFlags(version string) string {
+ releaseTag, date := releaseTag(version)
+ copyrightYear := fmt.Sprintf("%d", date.Year())
+
+ var ldflagsStr string
+ ldflagsStr = "-s -w -X github.com/parseablehq/pb/cmd.Version=" + version + " "
+ ldflagsStr = ldflagsStr + "-X github.com/parseablehq/pb/cmd.CopyrightYear=" + copyrightYear + " "
+ ldflagsStr = ldflagsStr + "-X github.com/parseablehq/pb/cmd.ReleaseTag=" + releaseTag + " "
+ ldflagsStr = ldflagsStr + "-X github.com/parseablehq/pb/cmd.CommitID=" + commitID() + " "
+ ldflagsStr = ldflagsStr + "-X github.com/parseablehq/pb/cmd.ShortCommitID=" + commitID()[:12]
+ return ldflagsStr
+}
+
+// releaseTag prints release tag to the console for easy git tagging.
+func releaseTag(version string) (string, time.Time) {
+ relPrefix := "DEVELOPMENT"
+ if prefix := os.Getenv("PB_RELEASE"); prefix != "" {
+ relPrefix = prefix
+ }
+
+ relSuffix := ""
+ if hotfix := os.Getenv("PB_HOTFIX"); hotfix != "" {
+ relSuffix = hotfix
+ }
+
+ relTag := strings.ReplaceAll(version, " ", "-")
+ relTag = strings.ReplaceAll(relTag, ":", "-")
+ t, err := time.Parse("2006-01-02T15-04-05Z", relTag)
+ if err != nil {
+ panic(err)
+ }
+
+ relTag = strings.ReplaceAll(relTag, ",", "")
+ relTag = relPrefix + "." + relTag
+ if relSuffix != "" {
+ relTag += "." + relSuffix
+ }
+
+ return relTag, t
+}
+
+// commitID returns the abbreviated commit-id hash of the last commit.
+func commitID() string {
+ // git log --format="%h" -n1
+ var (
+ commit []byte
+ e error
+ )
+ cmdName := "git"
+ cmdArgs := []string{"log", "--format=%H", "-n1"}
+ if commit, e = exec.Command(cmdName, cmdArgs...).Output(); e != nil {
+ fmt.Fprintln(os.Stderr, "Error generating git commit-id: ", e)
+ os.Exit(1)
+ }
+
+ return strings.TrimSpace(string(commit))
+}
+
+func commitTime() time.Time {
+ // git log --format=%cD -n1
+ var (
+ commitUnix []byte
+ err error
+ )
+ cmdName := "git"
+ cmdArgs := []string{"log", "--format=%cI", "-n1"}
+ if commitUnix, err = exec.Command(cmdName, cmdArgs...).Output(); err != nil {
+ fmt.Fprintln(os.Stderr, "Error generating git commit-time: ", err)
+ os.Exit(1)
+ }
+
+ t, err := time.Parse(time.RFC3339, strings.TrimSpace(string(commitUnix)))
+ if err != nil {
+ fmt.Fprintln(os.Stderr, "Error generating git commit-time: ", err)
+ os.Exit(1)
+ }
+
+ return t.UTC()
+}
+
+func main() {
+ var version string
+ if len(os.Args) > 1 {
+ version = os.Args[1]
+ } else {
+ version = commitTime().Format(time.RFC3339)
+ }
+
+ fmt.Println(genLDFlags(version))
+}
diff --git a/cmd/client.go b/cmd/client.go
index 10ff572..1696c1e 100644
--- a/cmd/client.go
+++ b/cmd/client.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -25,13 +24,13 @@ import (
"time"
)
-type HttpClient struct {
+type HTTPClient struct {
client http.Client
profile *config.Profile
}
-func DefaultClient() HttpClient {
- return HttpClient{
+func DefaultClient() HTTPClient {
+ return HTTPClient{
client: http.Client{
Timeout: 60 * time.Second,
},
@@ -39,13 +38,13 @@ func DefaultClient() HttpClient {
}
}
-func (client *HttpClient) baseApiUrl(path string) (x string) {
- x, _ = url.JoinPath(client.profile.Url, "api/v1/", path)
+func (client *HTTPClient) baseAPIURL(path string) (x string) {
+ x, _ = url.JoinPath(client.profile.URL, "api/v1/", path)
return
}
-func (client *HttpClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) {
- req, err = http.NewRequest(method, client.baseApiUrl(path), body)
+func (client *HTTPClient) NewRequest(method string, path string, body io.Reader) (req *http.Request, err error) {
+ req, err = http.NewRequest(method, client.baseAPIURL(path), body)
if err != nil {
return
}
diff --git a/cmd/pre.go b/cmd/pre.go
index 19ce3d8..6b969b0 100644
--- a/cmd/pre.go
+++ b/cmd/pre.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -27,21 +26,20 @@ import (
var DefaultProfile config.Profile
-// Check if a profile exists.
+// PreRunDefaultProfile if a profile exists.
// This is required by mostly all commands except profile
-func PreRunDefaultProfile(cmd *cobra.Command, args []string) error {
+func PreRunDefaultProfile(_ *cobra.Command, _ []string) error {
conf, err := config.ReadConfigFromFile()
- if err != nil {
- if os.IsNotExist(err) {
- return errors.New("no config found to run this command. add a profile using pb profile command")
- } else {
- return err
- }
+ if os.IsNotExist(err) {
+ return errors.New("no config found to run this command. add a profile using pb profile command")
+ } else if err != nil {
+ return err
}
- if conf.Profiles == nil || conf.Default_profile == "" {
+
+ if conf.Profiles == nil || conf.DefaultProfile == "" {
return errors.New("no profile is configured to run this command. please create one using profile command")
}
- DefaultProfile = conf.Profiles[conf.Default_profile]
+ DefaultProfile = conf.Profiles[conf.DefaultProfile]
return nil
}
diff --git a/cmd/profile.go b/cmd/profile.go
index d94a3f1..f259b03 100644
--- a/cmd/profile.go
+++ b/cmd/profile.go
@@ -1,236 +1,228 @@
-// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
-//
-// This file is part of MinIO Object Storage stack
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package cmd
-
-import (
- "errors"
- "fmt"
- "net/url"
- "os"
- "pb/pkg/config"
- "pb/pkg/model/credential"
- "pb/pkg/model/defaultprofile"
-
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/muesli/termenv"
- "github.com/spf13/cobra"
-)
-
-type ProfileListItem struct {
- title, url, user string
-}
-
-func (item *ProfileListItem) Render(highlight bool) string {
- if highlight {
- render := fmt.Sprintf(
- "%s\n%s\n%s",
- selectedStyle.Render(item.title),
- selectedStyleAlt.Render(fmt.Sprintf("url: %s", item.url)),
- selectedStyleAlt.Render(fmt.Sprintf("user: %s", item.user)),
- )
- return selectedItemOuter.Render(render)
- } else {
- render := fmt.Sprintf(
- "%s\n%s\n%s",
- standardStyle.Render(item.title),
- standardStyleAlt.Render(fmt.Sprintf("url: %s", item.url)),
- standardStyleAlt.Render(fmt.Sprintf("user: %s", item.user)),
- )
- return itemOuter.Render(render)
- }
-}
-
-var AddProfileCmd = &cobra.Command{
- Use: "add profile-name url ",
- Example: " pb profile add local_parseable http://0.0.0.0:8000 admin admin",
- Short: "Add a new profile",
- Long: "Add a new profile to the config file",
- Args: func(cmd *cobra.Command, args []string) error {
- if err := cobra.MinimumNArgs(2)(cmd, args); err != nil {
- return err
- }
- if err := cobra.MaximumNArgs(4)(cmd, args); err != nil {
- return err
- }
- return nil
- },
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- url, err := url.Parse(args[1])
- if err != nil {
- return err
- }
-
- var username string
- var password string
-
- if len(args) < 4 {
- _m, err := tea.NewProgram(credential.New()).Run()
- if err != nil {
- fmt.Printf("Alas, there's been an error: %v", err)
- os.Exit(1)
- }
- m := _m.(credential.Model)
-
- username, password = m.Values()
- } else {
- username = args[2]
- password = args[3]
- }
-
- profile := config.Profile{
- Url: url.String(),
- Username: username,
- Password: password,
- }
-
- file_config, err := config.ReadConfigFromFile()
-
- if err != nil {
- // create new file
- new_config := config.Config{
- Profiles: map[string]config.Profile{
- name: profile,
- },
- Default_profile: name,
- }
- err = config.WriteConfigToFile(&new_config)
- return err
- } else {
- if file_config.Profiles == nil {
- file_config.Profiles = make(map[string]config.Profile)
- }
- file_config.Profiles[name] = profile
- if file_config.Default_profile == "" {
- file_config.Default_profile = name
- }
- config.WriteConfigToFile(file_config)
- }
-
- return nil
- },
-}
-
-var RemoveProfileCmd = &cobra.Command{
- Use: "remove profile-name",
- Aliases: []string{"rm"},
- Example: " pb profile remove local_parseable",
- Args: cobra.ExactArgs(1),
- Short: "Delete a profile",
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- file_config, err := config.ReadConfigFromFile()
- if err != nil {
- return nil
- }
-
- _, exists := file_config.Profiles[name]
- if exists {
- delete(file_config.Profiles, name)
- if len(file_config.Profiles) == 0 {
- file_config.Default_profile = ""
- }
- config.WriteConfigToFile(file_config)
- fmt.Printf("Deleted profile %s\n", styleBold.Render(name))
- } else {
- fmt.Printf("No profile found with the name: %s", styleBold.Render(name))
- }
-
- return nil
- },
-}
-
-var DefaultProfileCmd = &cobra.Command{
- Use: "default profile-name",
- Args: cobra.MaximumNArgs(1),
- Short: "Set default profile to use with all commands",
- Example: " pb profile default local_parseable",
- RunE: func(cmd *cobra.Command, args []string) error {
- var name string
-
- file_config, err := config.ReadConfigFromFile()
- if err != nil {
- return nil
- }
-
- if len(args) > 0 {
- name = args[0]
- } else {
- model := defaultprofile.New(file_config.Profiles)
- _m, err := tea.NewProgram(model).Run()
- if err != nil {
- fmt.Printf("Alas, there's been an error: %v", err)
- os.Exit(1)
- }
- m := _m.(defaultprofile.Model)
- termenv.DefaultOutput().ClearLines(lipgloss.Height(model.View()) - 1)
- if m.Success {
- name = m.Choice
- } else {
- return nil
- }
- }
-
- _, exists := file_config.Profiles[name]
- if exists {
- file_config.Default_profile = name
- } else {
- name = lipgloss.NewStyle().Bold(true).Render(name)
- err := fmt.Sprintf("profile %s does not exist", styleBold.Render(name))
- return errors.New(err)
- }
-
- config.WriteConfigToFile(file_config)
- fmt.Printf("%s is now set as default profile\n", styleBold.Render(name))
- return nil
- },
-}
-
-var ListProfileCmd = &cobra.Command{
- Use: "list profiles",
- Short: "List all added profiles",
- Example: " pb profile list",
- RunE: func(cmd *cobra.Command, args []string) error {
- file_config, err := config.ReadConfigFromFile()
- if err != nil {
- return nil
- }
-
- if len(file_config.Profiles) != 0 {
- println()
- }
-
- row := 0
- for key, value := range file_config.Profiles {
- item := ProfileListItem{key, value.Url, value.Username}
- fmt.Println(item.Render(file_config.Default_profile == key))
- row += 1
- fmt.Println()
- }
- return nil
- },
-}
-
-func Max(a int, b int) int {
- if a >= b {
- return a
- } else {
- return b
- }
-}
+// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "errors"
+ "fmt"
+ "net/url"
+ "os"
+ "pb/pkg/config"
+ "pb/pkg/model/credential"
+ "pb/pkg/model/defaultprofile"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/muesli/termenv"
+ "github.com/spf13/cobra"
+)
+
+// ProfileListItem is a struct to hold the profile list items
+type ProfileListItem struct {
+ title, url, user string
+}
+
+func (item *ProfileListItem) Render(highlight bool) string {
+ if highlight {
+ render := fmt.Sprintf(
+ "%s\n%s\n%s",
+ selectedStyle.Render(item.title),
+ selectedStyleAlt.Render(fmt.Sprintf("url: %s", item.url)),
+ selectedStyleAlt.Render(fmt.Sprintf("user: %s", item.user)),
+ )
+ return selectedItemOuter.Render(render)
+ }
+ render := fmt.Sprintf(
+ "%s\n%s\n%s",
+ standardStyle.Render(item.title),
+ standardStyleAlt.Render(fmt.Sprintf("url: %s", item.url)),
+ standardStyleAlt.Render(fmt.Sprintf("user: %s", item.user)),
+ )
+ return itemOuter.Render(render)
+}
+
+var AddProfileCmd = &cobra.Command{
+ Use: "add profile-name url ",
+ Example: " pb profile add local_parseable http://0.0.0.0:8000 admin admin",
+ Short: "Add a new profile",
+ Long: "Add a new profile to the config file",
+ Args: func(cmd *cobra.Command, args []string) error {
+ if err := cobra.MinimumNArgs(2)(cmd, args); err != nil {
+ return err
+ }
+ return cobra.MaximumNArgs(4)(cmd, args)
+ },
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ url, err := url.Parse(args[1])
+ if err != nil {
+ return err
+ }
+
+ var username string
+ var password string
+
+ if len(args) < 4 {
+ _m, err := tea.NewProgram(credential.New()).Run()
+ if err != nil {
+ fmt.Printf("Alas, there's been an error: %v", err)
+ os.Exit(1)
+ }
+ m := _m.(credential.Model)
+
+ username, password = m.Values()
+ } else {
+ username = args[2]
+ password = args[3]
+ }
+
+ profile := config.Profile{
+ URL: url.String(),
+ Username: username,
+ Password: password,
+ }
+
+ fileConfig, err := config.ReadConfigFromFile()
+ if err != nil {
+ // create new file
+ newConfig := config.Config{
+ Profiles: map[string]config.Profile{
+ name: profile,
+ },
+ DefaultProfile: name,
+ }
+ err = config.WriteConfigToFile(&newConfig)
+ return err
+ }
+ if fileConfig.Profiles == nil {
+ fileConfig.Profiles = make(map[string]config.Profile)
+ }
+ fileConfig.Profiles[name] = profile
+ if fileConfig.DefaultProfile == "" {
+ fileConfig.DefaultProfile = name
+ }
+ config.WriteConfigToFile(fileConfig)
+
+ return nil
+ },
+}
+
+var RemoveProfileCmd = &cobra.Command{
+ Use: "remove profile-name",
+ Aliases: []string{"rm"},
+ Example: " pb profile remove local_parseable",
+ Args: cobra.ExactArgs(1),
+ Short: "Delete a profile",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ fileConfig, err := config.ReadConfigFromFile()
+ if err != nil {
+ return nil
+ }
+
+ _, exists := fileConfig.Profiles[name]
+ if exists {
+ delete(fileConfig.Profiles, name)
+ if len(fileConfig.Profiles) == 0 {
+ fileConfig.DefaultProfile = ""
+ }
+ config.WriteConfigToFile(fileConfig)
+ fmt.Printf("Deleted profile %s\n", styleBold.Render(name))
+ } else {
+ fmt.Printf("No profile found with the name: %s", styleBold.Render(name))
+ }
+
+ return nil
+ },
+}
+
+var DefaultProfileCmd = &cobra.Command{
+ Use: "default profile-name",
+ Args: cobra.MaximumNArgs(1),
+ Short: "Set default profile to use with all commands",
+ Example: " pb profile default local_parseable",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var name string
+
+ fileConfig, err := config.ReadConfigFromFile()
+ if err != nil {
+ return nil
+ }
+
+ if len(args) > 0 {
+ name = args[0]
+ } else {
+ model := defaultprofile.New(fileConfig.Profiles)
+ _m, err := tea.NewProgram(model).Run()
+ if err != nil {
+ fmt.Printf("Alas, there's been an error: %v", err)
+ os.Exit(1)
+ }
+ m := _m.(defaultprofile.Model)
+ termenv.DefaultOutput().ClearLines(lipgloss.Height(model.View()) - 1)
+ if m.Success {
+ name = m.Choice
+ } else {
+ return nil
+ }
+ }
+
+ _, exists := fileConfig.Profiles[name]
+ if exists {
+ fileConfig.DefaultProfile = name
+ } else {
+ name = lipgloss.NewStyle().Bold(true).Render(name)
+ err := fmt.Sprintf("profile %s does not exist", styleBold.Render(name))
+ return errors.New(err)
+ }
+
+ config.WriteConfigToFile(fileConfig)
+ fmt.Printf("%s is now set as default profile\n", styleBold.Render(name))
+ return nil
+ },
+}
+
+var ListProfileCmd = &cobra.Command{
+ Use: "list profiles",
+ Short: "List all added profiles",
+ Example: " pb profile list",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ fileConfig, err := config.ReadConfigFromFile()
+ if err != nil {
+ return nil
+ }
+
+ if len(fileConfig.Profiles) != 0 {
+ println()
+ }
+
+ row := 0
+ for key, value := range fileConfig.Profiles {
+ item := ProfileListItem{key, value.URL, value.Username}
+ fmt.Println(item.Render(fileConfig.DefaultProfile == key))
+ row++
+ fmt.Println()
+ }
+ return nil
+ },
+}
+
+func Max(a int, b int) int {
+ if a >= b {
+ return a
+ }
+ return b
+}
diff --git a/cmd/stream.go b/cmd/stream.go
index 1a12e18..5227677 100644
--- a/cmd/stream.go
+++ b/cmd/stream.go
@@ -1,354 +1,354 @@
-// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
-//
-// This file is part of MinIO Object Storage stack
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package cmd
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "strconv"
- "strings"
- "time"
-
- "github.com/dustin/go-humanize"
- "github.com/spf13/cobra"
-)
-
-type StreamStatsData struct {
- Ingestion struct {
- Count int `json:"count"`
- Format string `json:"format"`
- Size string `json:"size"`
- } `json:"ingestion"`
- Storage struct {
- Format string `json:"format"`
- Size string `json:"size"`
- } `json:"storage"`
- Stream string `json:"stream"`
- Time time.Time `json:"time"`
-}
-
-type StreamRetentionData []struct {
- Description string `json:"description"`
- Action string `json:"action"`
- Duration string `json:"duration"`
-}
-
-type StreamAlertData struct {
- Alerts []struct {
- Message string `json:"message"`
- Name string `json:"name"`
- Rule struct {
- Config struct {
- Column string `json:"column"`
- Operator string `json:"operator"`
- Repeats int `json:"repeats"`
- Value int `json:"value"`
- } `json:"config"`
- Type string `json:"type"`
- } `json:"rule"`
- Targets []struct {
- Endpoint string `json:"endpoint"`
- Password string `json:"password,omitempty"`
- Repeat struct {
- Interval string `json:"interval"`
- Times int `json:"times"`
- } `json:"repeat"`
- SkipTLSCheck bool `json:"skip_tls_check,omitempty"`
- Type string `json:"type"`
- Username string `json:"username,omitempty"`
- Headers struct {
- Authorization string `json:"Authorization"`
- } `json:"headers,omitempty"`
- } `json:"targets"`
- } `json:"alerts"`
- Version string `json:"version"`
-}
-
-var AddStreamCmd = &cobra.Command{
- Use: "add stream-name",
- Example: " pb stream add backend_logs",
- Short: "Create a new stream",
- Args: cobra.ExactArgs(1),
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- client := DefaultClient()
- req, err := client.NewRequest("PUT", "logstream/"+name, nil)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- if resp.StatusCode == 200 {
- fmt.Printf("Created stream %s\n", styleBold.Render(name))
- } else {
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- body := string(bytes)
- defer resp.Body.Close()
- fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- }
-
- return nil
- },
-}
-
-var StatStreamCmd = &cobra.Command{
- Use: "info stream-name",
- Example: " pb stream info backend_logs",
- Short: "Get statistics for a stream",
- Args: cobra.ExactArgs(1),
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- client := DefaultClient()
-
- stats, err := fetchStats(&client, name)
- if err != nil {
- return err
- }
-
- ingestion_count := stats.Ingestion.Count
- ingestion_size, _ := strconv.Atoi(strings.TrimRight(stats.Ingestion.Size, " Bytes"))
- storage_size, _ := strconv.Atoi(strings.TrimRight(stats.Storage.Size, " Bytes"))
-
- retention, err := fetchRetention(&client, name)
- if err != nil {
- return err
- }
-
- is_rentention_set := len(retention) > 0
-
- fmt.Println(styleBold.Render("Info:"))
- fmt.Printf(" Event Count: %d\n", ingestion_count)
- fmt.Printf(" Ingestion Size: %s\n", humanize.Bytes(uint64(ingestion_size)))
- fmt.Printf(" Storage Size: %s\n", humanize.Bytes(uint64(storage_size)))
- fmt.Printf(
- " Compression Ratio: %.2f%s\n",
- 100-(float64(storage_size)/float64(ingestion_size))*100, "%")
- fmt.Println()
-
- if is_rentention_set {
- fmt.Println(styleBold.Render("Retention:"))
- for _, item := range retention {
- fmt.Printf(" Action: %s\n", styleBold.Render(item.Action))
- fmt.Printf(" Duration: %s\n", styleBold.Render(item.Duration))
- fmt.Println()
- }
- } else {
- fmt.Println(styleBold.Render("No retention period set on stream\n"))
- }
-
- alerts_data, err := fetchAlerts(&client, name)
- if err != nil {
- return err
- }
- alerts := alerts_data.Alerts
-
- is_alerts_set := len(alerts) > 0
-
- if is_alerts_set {
- fmt.Println(styleBold.Render("Alerts:"))
- for _, alert := range alerts {
- fmt.Printf(" Alert: %s\n", styleBold.Render(alert.Name))
- rule_fmt := fmt.Sprintf(
- "%s %s %s repeated %d times",
- alert.Rule.Config.Column,
- alert.Rule.Config.Operator,
- fmt.Sprint(alert.Rule.Config.Value),
- alert.Rule.Config.Repeats,
- )
- fmt.Printf(" Rule: %s\n", rule_fmt)
- fmt.Printf(" Targets: ")
- for _, target := range alert.Targets {
- fmt.Printf("%s, ", target.Type)
- }
- fmt.Print("\n\n")
- }
- } else {
- fmt.Println(styleBold.Render("No alerts set on stream\n"))
- }
-
- return nil
- },
-}
-
-var RemoveStreamCmd = &cobra.Command{
- Use: "remove stream-name",
- Aliases: []string{"rm"},
- Example: " pb stream remove backend_logs",
- Short: "Delete a stream",
- Args: cobra.ExactArgs(1),
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- client := DefaultClient()
- req, err := client.NewRequest("DELETE", "logstream/"+name, nil)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- if resp.StatusCode == 200 {
- fmt.Printf("Removed stream %s", styleBold.Render(name))
- } else {
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- body := string(bytes)
- defer resp.Body.Close()
-
- fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- }
-
- return nil
- },
-}
-
-var ListStreamCmd = &cobra.Command{
- Use: "list",
- Short: "List all streams",
- Example: " pb stream list",
- RunE: func(cmd *cobra.Command, args []string) error {
- client := DefaultClient()
- req, err := client.NewRequest("GET", "logstream", nil)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- if resp.StatusCode == 200 {
- items := []map[string]string{}
- err = json.NewDecoder(resp.Body).Decode(&items)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
- for _, item := range items {
- fmt.Println(item["name"])
- }
- } else {
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- body := string(bytes)
- fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- }
-
- return nil
- },
-}
-
-func fetchStats(client *HttpClient, name string) (data StreamStatsData, err error) {
- req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/stats", name), nil)
- if err != nil {
- return
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return
- }
-
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- if resp.StatusCode == 200 {
- err = json.Unmarshal(bytes, &data)
- return
- } else {
- body := string(bytes)
- body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- err = errors.New(body)
- }
- return
-}
-
-func fetchRetention(client *HttpClient, name string) (data StreamRetentionData, err error) {
- req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/retention", name), nil)
- if err != nil {
- return
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return
- }
-
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- if resp.StatusCode == 200 {
- err = json.Unmarshal(bytes, &data)
- return
- } else {
- body := string(bytes)
- body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- err = errors.New(body)
- }
- return
-}
-
-func fetchAlerts(client *HttpClient, name string) (data StreamAlertData, err error) {
- req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/alert", name), nil)
- if err != nil {
- return
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return
- }
-
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- if resp.StatusCode == 200 {
- err = json.Unmarshal(bytes, &data)
- return
- } else {
- body := string(bytes)
- body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- err = errors.New(body)
- }
- return
-}
+// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/dustin/go-humanize"
+ "github.com/spf13/cobra"
+)
+
+// StreamStatsData is the data structure for stream stats
+type StreamStatsData struct {
+ Ingestion struct {
+ Count int `json:"count"`
+ Format string `json:"format"`
+ Size string `json:"size"`
+ } `json:"ingestion"`
+ Storage struct {
+ Format string `json:"format"`
+ Size string `json:"size"`
+ } `json:"storage"`
+ Stream string `json:"stream"`
+ Time time.Time `json:"time"`
+}
+
+// StreamRetentionData is the data structure for stream retention
+type StreamRetentionData []struct {
+ Description string `json:"description"`
+ Action string `json:"action"`
+ Duration string `json:"duration"`
+}
+
+// StreamAlertData is the data structure for stream alerts
+type StreamAlertData struct {
+ Alerts []struct {
+ Message string `json:"message"`
+ Name string `json:"name"`
+ Rule struct {
+ Config struct {
+ Column string `json:"column"`
+ Operator string `json:"operator"`
+ Repeats int `json:"repeats"`
+ Value int `json:"value"`
+ } `json:"config"`
+ Type string `json:"type"`
+ } `json:"rule"`
+ Targets []struct {
+ Endpoint string `json:"endpoint"`
+ Password string `json:"password,omitempty"`
+ Repeat struct {
+ Interval string `json:"interval"`
+ Times int `json:"times"`
+ } `json:"repeat"`
+ SkipTLSCheck bool `json:"skip_tls_check,omitempty"`
+ Type string `json:"type"`
+ Username string `json:"username,omitempty"`
+ Headers struct {
+ Authorization string `json:"Authorization"`
+ } `json:"headers,omitempty"`
+ } `json:"targets"`
+ } `json:"alerts"`
+ Version string `json:"version"`
+}
+
+// AddStreamCmd is the parent command for stream
+var AddStreamCmd = &cobra.Command{
+ Use: "add stream-name",
+ Example: " pb stream add backend_logs",
+ Short: "Create a new stream",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ client := DefaultClient()
+ req, err := client.NewRequest("PUT", "logstream/"+name, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode == 200 {
+ fmt.Printf("Created stream %s\n", styleBold.Render(name))
+ } else {
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ body := string(bytes)
+ defer resp.Body.Close()
+ fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ }
+
+ return nil
+ },
+}
+
+// StatStreamCmd is the stat command for stream
+var StatStreamCmd = &cobra.Command{
+ Use: "info stream-name",
+ Example: " pb stream info backend_logs",
+ Short: "Get statistics for a stream",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ client := DefaultClient()
+
+ stats, err := fetchStats(&client, name)
+ if err != nil {
+ return err
+ }
+
+ ingestionCount := stats.Ingestion.Count
+ ingestionSize, _ := strconv.Atoi(strings.TrimRight(stats.Ingestion.Size, " Bytes"))
+ storageSize, _ := strconv.Atoi(strings.TrimRight(stats.Storage.Size, " Bytes"))
+
+ retention, err := fetchRetention(&client, name)
+ if err != nil {
+ return err
+ }
+
+ isRententionSet := len(retention) > 0
+
+ fmt.Println(styleBold.Render("Info:"))
+ fmt.Printf(" Event Count: %d\n", ingestionCount)
+ fmt.Printf(" Ingestion Size: %s\n", humanize.Bytes(uint64(ingestionSize)))
+ fmt.Printf(" Storage Size: %s\n", humanize.Bytes(uint64(storageSize)))
+ fmt.Printf(
+ " Compression Ratio: %.2f%s\n",
+ 100-(float64(storageSize)/float64(ingestionSize))*100, "%")
+ fmt.Println()
+
+ if isRententionSet {
+ fmt.Println(styleBold.Render("Retention:"))
+ for _, item := range retention {
+ fmt.Printf(" Action: %s\n", styleBold.Render(item.Action))
+ fmt.Printf(" Duration: %s\n", styleBold.Render(item.Duration))
+ fmt.Println()
+ }
+ } else {
+ fmt.Println(styleBold.Render("No retention period set on stream\n"))
+ }
+
+ alertsData, err := fetchAlerts(&client, name)
+ if err != nil {
+ return err
+ }
+ alerts := alertsData.Alerts
+
+ isAlertsSet := len(alerts) > 0
+
+ if isAlertsSet {
+ fmt.Println(styleBold.Render("Alerts:"))
+ for _, alert := range alerts {
+ fmt.Printf(" Alert: %s\n", styleBold.Render(alert.Name))
+ ruleFmt := fmt.Sprintf(
+ "%s %s %s repeated %d times",
+ alert.Rule.Config.Column,
+ alert.Rule.Config.Operator,
+ fmt.Sprint(alert.Rule.Config.Value),
+ alert.Rule.Config.Repeats,
+ )
+ fmt.Printf(" Rule: %s\n", ruleFmt)
+ fmt.Printf(" Targets: ")
+ for _, target := range alert.Targets {
+ fmt.Printf("%s, ", target.Type)
+ }
+ fmt.Print("\n\n")
+ }
+ } else {
+ fmt.Println(styleBold.Render("No alerts set on stream\n"))
+ }
+
+ return nil
+ },
+}
+
+var RemoveStreamCmd = &cobra.Command{
+ Use: "remove stream-name",
+ Aliases: []string{"rm"},
+ Example: " pb stream remove backend_logs",
+ Short: "Delete a stream",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ client := DefaultClient()
+ req, err := client.NewRequest("DELETE", "logstream/"+name, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode == 200 {
+ fmt.Printf("Removed stream %s", styleBold.Render(name))
+ } else {
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ body := string(bytes)
+ defer resp.Body.Close()
+
+ fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ }
+
+ return nil
+ },
+}
+
+var ListStreamCmd = &cobra.Command{
+ Use: "list",
+ Short: "List all streams",
+ Example: " pb stream list",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ client := DefaultClient()
+ req, err := client.NewRequest("GET", "logstream", nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode == 200 {
+ items := []map[string]string{}
+ err = json.NewDecoder(resp.Body).Decode(&items)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ for _, item := range items {
+ fmt.Println(item["name"])
+ }
+ } else {
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ body := string(bytes)
+ fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ }
+
+ return nil
+ },
+}
+
+func fetchStats(client *HTTPClient, name string) (data StreamStatsData, err error) {
+ req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/stats", name), nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return
+ }
+
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ err = json.Unmarshal(bytes, &data)
+ } else {
+ body := string(bytes)
+ body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ err = errors.New(body)
+ }
+ return
+}
+
+func fetchRetention(client *HTTPClient, name string) (data StreamRetentionData, err error) {
+ req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/retention", name), nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return
+ }
+
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ err = json.Unmarshal(bytes, &data)
+ } else {
+ body := string(bytes)
+ body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ err = errors.New(body)
+ }
+ return
+}
+
+func fetchAlerts(client *HTTPClient, name string) (data StreamAlertData, err error) {
+ req, err := client.NewRequest("GET", fmt.Sprintf("logstream/%s/alert", name), nil)
+ if err != nil {
+ return
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return
+ }
+
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ err = json.Unmarshal(bytes, &data)
+ } else {
+ body := string(bytes)
+ body = fmt.Sprintf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ err = errors.New(body)
+ }
+ return
+}
diff --git a/cmd/style.go b/cmd/style.go
index f02430a..dea7e61 100644
--- a/cmd/style.go
+++ b/cmd/style.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
diff --git a/cmd/user.go b/cmd/user.go
index f360b55..ec1afe7 100644
--- a/cmd/user.go
+++ b/cmd/user.go
@@ -1,285 +1,283 @@
-// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
-//
-// This file is part of MinIO Object Storage stack
-//
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Affero General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// This program is distributed in the hope that it will be useful
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Affero General Public License for more details.
-//
-// You should have received a copy of the GNU Affero General Public License
-// along with this program. If not, see .
-
-package cmd
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "os"
- "pb/pkg/model/role"
- "strings"
- "sync"
-
- tea "github.com/charmbracelet/bubbletea"
- "github.com/charmbracelet/lipgloss"
- "github.com/spf13/cobra"
- "golang.org/x/exp/slices"
-)
-
-type RoleResource struct {
- Stream string `json:"stream,omitempty"`
- Tag string `json:"tag,omitempty"`
-}
-
-type UserRoleData struct {
- Privilege string `json:"privilege"`
- Resource *RoleResource `json:"resource,omitempty"`
-}
-
-func (user *UserRoleData) Render() string {
- var s strings.Builder
- s.WriteString(standardStyle.Render("Privilege: "))
- s.WriteString(standardStyleAlt.Render(user.Privilege))
- s.WriteString("\n")
- if user.Resource != nil {
- if user.Resource.Stream != "" {
- s.WriteString(standardStyle.Render("Stream: "))
- s.WriteString(standardStyleAlt.Render(user.Resource.Stream))
- s.WriteString("\n")
- }
- if user.Resource.Tag != "" {
- s.WriteString(standardStyle.Render("Tag: "))
- s.WriteString(standardStyleAlt.Render(user.Resource.Tag))
- s.WriteString("\n")
- }
- }
-
- return s.String()
-}
-
-type FetchUserRoleRes struct {
- data []UserRoleData
- err error
-}
-
-var AddUserCmd = &cobra.Command{
- Use: "add user-name",
- Example: " pb user add bob",
- Short: "Add a new user",
- Args: cobra.ExactArgs(1),
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
-
- var users []string
- client := DefaultClient()
- if err := fetchUsers(&client, &users); err != nil {
- return err
- }
-
- if slices.Contains(users, name) {
- fmt.Println("user already exists")
- return nil
- }
-
- _m, err := tea.NewProgram(role.New()).Run()
- if err != nil {
- fmt.Printf("Alas, there's been an error: %v", err)
- os.Exit(1)
- }
- m := _m.(role.Model)
-
- privilege := m.Selection.Value()
- stream := m.Stream.Value()
- tag := m.Tag.Value()
-
- if !m.Success {
- fmt.Println("aborted by user")
- return nil
- }
-
- var putBody io.Reader
-
- // set role
- if privilege != "none" {
- roleData := UserRoleData{
- Privilege: privilege,
- }
- switch privilege {
- case "writer":
- roleData.Resource = &RoleResource{
- Stream: stream,
- }
- case "reader":
- roleData.Resource = &RoleResource{
- Stream: stream,
- }
- if tag != "" {
- roleData.Resource.Tag = tag
- }
- }
- roleDataJson, _ := json.Marshal([]UserRoleData{roleData})
- putBody = bytes.NewBuffer(roleDataJson)
- }
- req, err := client.NewRequest("PUT", "user/"+name, putBody)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- body := string(bytes)
- defer resp.Body.Close()
-
- if resp.StatusCode == 200 {
- fmt.Printf("Added user %s \nPassword is: %s\n", name, body)
- } else {
- fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- }
-
- return nil
- },
-}
-
-var RemoveUserCmd = &cobra.Command{
- Use: "remove user-name",
- Aliases: []string{"rm"},
- Example: " pb user remove bob",
- Short: "Delete a user",
- Args: cobra.ExactArgs(1),
- RunE: func(cmd *cobra.Command, args []string) error {
- name := args[0]
- client := DefaultClient()
- req, err := client.NewRequest("DELETE", "user/"+name, nil)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- if resp.StatusCode == 200 {
- fmt.Printf("Removed user %s\n", styleBold.Render(name))
- } else {
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- body := string(bytes)
- defer resp.Body.Close()
-
- fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
- }
-
- return nil
- },
-}
-
-var ListUserCmd = &cobra.Command{
- Use: "list",
- Short: "List all users",
- Example: " pb user list",
- RunE: func(cmd *cobra.Command, args []string) error {
- var users []string
- client := DefaultClient()
- err := fetchUsers(&client, &users)
- if err != nil {
- return err
- }
-
- role_responses := make([]FetchUserRoleRes, len(users))
- wsg := sync.WaitGroup{}
- wsg.Add(len(users))
-
- for idx, user := range users {
- idx := idx
- user := user
- client := &client
- go func() {
- role_responses[idx] = fetchUserRoles(client, user)
- wsg.Done()
- }()
- }
-
- wsg.Wait()
- fmt.Println()
- for idx, user := range users {
- roles := role_responses[idx]
- fmt.Print("• ")
- fmt.Println(standardStyleBold.Bold(true).Render(user))
- if roles.err == nil {
- for _, role := range roles.data {
- fmt.Println(lipgloss.NewStyle().PaddingLeft(3).Render(role.Render()))
- }
- } else {
- fmt.Println(roles.err)
- }
- }
-
- return nil
- },
-}
-
-func fetchUsers(client *HttpClient, data *[]string) error {
- req, err := client.NewRequest("GET", "user", nil)
- if err != nil {
- return err
- }
-
- resp, err := client.client.Do(req)
- if err != nil {
- return err
- }
-
- bytes, err := io.ReadAll(resp.Body)
- if err != nil {
- return err
- }
- defer resp.Body.Close()
-
- if resp.StatusCode == 200 {
- err = json.Unmarshal(bytes, data)
- if err != nil {
- return err
- }
- } else {
- body := string(bytes)
- return fmt.Errorf("request failed\nstatus code: %s\nresponse: %s", resp.Status, body)
- }
-
- return nil
-}
-
-func fetchUserRoles(client *HttpClient, user string) (res FetchUserRoleRes) {
- req, err := client.NewRequest("GET", fmt.Sprintf("user/%s/role", user), nil)
- if err != nil {
- return
- }
- resp, err := client.client.Do(req)
- if err != nil {
- return
- }
- body, err := io.ReadAll(resp.Body)
- if err != nil {
- return
- }
- defer resp.Body.Close()
-
- res.err = json.Unmarshal(body, &res.data)
- return
-}
+// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+package cmd
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "os"
+ "pb/pkg/model/role"
+ "strings"
+ "sync"
+
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/lipgloss"
+ "github.com/spf13/cobra"
+ "golang.org/x/exp/slices"
+)
+
+type RoleResource struct {
+ Stream string `json:"stream,omitempty"`
+ Tag string `json:"tag,omitempty"`
+}
+
+type UserRoleData struct {
+ Privilege string `json:"privilege"`
+ Resource *RoleResource `json:"resource,omitempty"`
+}
+
+func (user *UserRoleData) Render() string {
+ var s strings.Builder
+ s.WriteString(standardStyle.Render("Privilege: "))
+ s.WriteString(standardStyleAlt.Render(user.Privilege))
+ s.WriteString("\n")
+ if user.Resource != nil {
+ if user.Resource.Stream != "" {
+ s.WriteString(standardStyle.Render("Stream: "))
+ s.WriteString(standardStyleAlt.Render(user.Resource.Stream))
+ s.WriteString("\n")
+ }
+ if user.Resource.Tag != "" {
+ s.WriteString(standardStyle.Render("Tag: "))
+ s.WriteString(standardStyleAlt.Render(user.Resource.Tag))
+ s.WriteString("\n")
+ }
+ }
+
+ return s.String()
+}
+
+type FetchUserRoleRes struct {
+ data []UserRoleData
+ err error
+}
+
+var AddUserCmd = &cobra.Command{
+ Use: "add user-name",
+ Example: " pb user add bob",
+ Short: "Add a new user",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+
+ var users []string
+ client := DefaultClient()
+ if err := fetchUsers(&client, &users); err != nil {
+ return err
+ }
+
+ if slices.Contains(users, name) {
+ fmt.Println("user already exists")
+ return nil
+ }
+
+ _m, err := tea.NewProgram(role.New()).Run()
+ if err != nil {
+ fmt.Printf("Alas, there's been an error: %v", err)
+ os.Exit(1)
+ }
+ m := _m.(role.Model)
+
+ privilege := m.Selection.Value()
+ stream := m.Stream.Value()
+ tag := m.Tag.Value()
+
+ if !m.Success {
+ fmt.Println("aborted by user")
+ return nil
+ }
+
+ var putBody io.Reader
+
+ // set role
+ if privilege != "none" {
+ roleData := UserRoleData{
+ Privilege: privilege,
+ }
+ switch privilege {
+ case "writer":
+ roleData.Resource = &RoleResource{
+ Stream: stream,
+ }
+ case "reader":
+ roleData.Resource = &RoleResource{
+ Stream: stream,
+ }
+ if tag != "" {
+ roleData.Resource.Tag = tag
+ }
+ }
+ roleDataJSON, _ := json.Marshal([]UserRoleData{roleData})
+ putBody = bytes.NewBuffer(roleDataJSON)
+ }
+ req, err := client.NewRequest("PUT", "user/"+name, putBody)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ body := string(bytes)
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ fmt.Printf("Added user %s \nPassword is: %s\n", name, body)
+ } else {
+ fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ }
+
+ return nil
+ },
+}
+
+var RemoveUserCmd = &cobra.Command{
+ Use: "remove user-name",
+ Aliases: []string{"rm"},
+ Example: " pb user remove bob",
+ Short: "Delete a user",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ client := DefaultClient()
+ req, err := client.NewRequest("DELETE", "user/"+name, nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ if resp.StatusCode == 200 {
+ fmt.Printf("Removed user %s\n", styleBold.Render(name))
+ } else {
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ body := string(bytes)
+ defer resp.Body.Close()
+
+ fmt.Printf("Request Failed\nStatus Code: %s\nResponse: %s\n", resp.Status, body)
+ }
+
+ return nil
+ },
+}
+
+var ListUserCmd = &cobra.Command{
+ Use: "list",
+ Short: "List all users",
+ Example: " pb user list",
+ RunE: func(cmd *cobra.Command, args []string) error {
+ var users []string
+ client := DefaultClient()
+ err := fetchUsers(&client, &users)
+ if err != nil {
+ return err
+ }
+
+ roleResponses := make([]FetchUserRoleRes, len(users))
+ wsg := sync.WaitGroup{}
+ wsg.Add(len(users))
+
+ for idx, user := range users {
+ idx := idx
+ user := user
+ client := &client
+ go func() {
+ roleResponses[idx] = fetchUserRoles(client, user)
+ wsg.Done()
+ }()
+ }
+
+ wsg.Wait()
+ fmt.Println()
+ for idx, user := range users {
+ roles := roleResponses[idx]
+ fmt.Print("• ")
+ fmt.Println(standardStyleBold.Bold(true).Render(user))
+ if roles.err == nil {
+ for _, role := range roles.data {
+ fmt.Println(lipgloss.NewStyle().PaddingLeft(3).Render(role.Render()))
+ }
+ } else {
+ fmt.Println(roles.err)
+ }
+ }
+
+ return nil
+ },
+}
+
+func fetchUsers(client *HTTPClient, data *[]string) error {
+ req, err := client.NewRequest("GET", "user", nil)
+ if err != nil {
+ return err
+ }
+
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return err
+ }
+
+ bytes, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode == 200 {
+ err = json.Unmarshal(bytes, data)
+ if err != nil {
+ return err
+ }
+ } else {
+ body := string(bytes)
+ return fmt.Errorf("request failed\nstatus code: %s\nresponse: %s", resp.Status, body)
+ }
+
+ return nil
+}
+
+func fetchUserRoles(client *HTTPClient, user string) (res FetchUserRoleRes) {
+ req, err := client.NewRequest("GET", fmt.Sprintf("user/%s/role", user), nil)
+ if err != nil {
+ return
+ }
+ resp, err := client.client.Do(req)
+ if err != nil {
+ return
+ }
+ body, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return
+ }
+ defer resp.Body.Close()
+
+ res.err = json.Unmarshal(body, &res.data)
+ return
+}
diff --git a/cmd/version.go b/cmd/version.go
index f4d407a..8c866f1 100644
--- a/cmd/version.go
+++ b/cmd/version.go
@@ -6,6 +6,7 @@ import (
"github.com/spf13/cobra"
)
+// VersionCmd is the command for printing version information
var VersionCmd = &cobra.Command{
Use: "version",
Short: "Print version",
@@ -13,6 +14,7 @@ var VersionCmd = &cobra.Command{
Example: " pb version",
}
+// PrintVersion prints version information
func PrintVersion(version string, commit string) {
fmt.Printf("\n%s \n\n", standardStyleAlt.Render("pb version"))
fmt.Printf("%s %s\n", standardStyleBold.Render("version: "), version)
diff --git a/main.go b/main.go
index cc64bcd..913b41a 100644
--- a/main.go
+++ b/main.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -31,8 +30,8 @@ import (
var (
// populated at build time
- PBVersion string
- PBCommit string
+ version string
+ commit string
)
var (
@@ -43,9 +42,9 @@ var (
defaultDuration = "10"
)
-func DefaultInitialProfile() config.Profile {
+func defaultInitialProfile() config.Profile {
return config.Profile{
- Url: "https://demo.parseable.io",
+ URL: "https://demo.parseable.io",
Username: "admin",
Password: "admin",
}
@@ -58,7 +57,7 @@ var cli = &cobra.Command{
Long: "\npb is a command line tool for Parseable",
Run: func(command *cobra.Command, args []string) {
if p, _ := command.Flags().GetBool(versionFlag); p {
- cmd.PrintVersion(PBVersion, PBCommit)
+ cmd.PrintVersion(version, commit)
}
},
}
@@ -133,10 +132,10 @@ func main() {
// Set as command
cmd.VersionCmd.Run = func(_ *cobra.Command, args []string) {
- cmd.PrintVersion(PBVersion, PBCommit)
+ cmd.PrintVersion(version, commit)
}
cli.AddCommand(cmd.VersionCmd)
- //set as flag
+ // set as flag
cli.Flags().BoolP(versionFlag, versionFlagShort, false, "Print version")
cli.CompletionOptions.HiddenDefaultCmd = true
@@ -144,8 +143,8 @@ func main() {
// create a default profile if file does not exist
if _, err := config.ReadConfigFromFile(); os.IsNotExist(err) {
conf := config.Config{
- Profiles: map[string]config.Profile{"demo": DefaultInitialProfile()},
- Default_profile: "demo",
+ Profiles: map[string]config.Profile{"demo": defaultInitialProfile()},
+ DefaultProfile: "demo",
}
config.WriteConfigToFile(&conf)
}
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 2174491..b90ec0c 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -26,32 +25,36 @@ import (
)
var (
- ConfigFilename = "config.toml"
- ConfigAppName = "parseable"
+ configFilename = "config.toml"
+ configAppName = "parseable"
)
-func ConfigPath() (string, error) {
+// Path returns user directory that can be used for the config file
+func Path() (string, error) {
dir, err := os.UserConfigDir()
if err != nil {
return "", err
}
- return path.Join(dir, ConfigAppName, ConfigFilename), nil
+ return path.Join(dir, configAppName, configFilename), nil
}
+// Config is the struct that holds the configuration
type Config struct {
- Profiles map[string]Profile
- Default_profile string
+ Profiles map[string]Profile
+ DefaultProfile string
}
+// Profile is the struct that holds the profile configuration
type Profile struct {
- Url string
+ URL string
Username string
Password string
}
+// WriteConfigToFile writes the configuration to the config file
func WriteConfigToFile(config *Config) error {
tomlData, _ := toml.Marshal(config)
- filePath, err := ConfigPath()
+ filePath, err := Path()
if err != nil {
return err
}
@@ -76,8 +79,9 @@ func WriteConfigToFile(config *Config) error {
return err
}
+// ReadConfigFromFile reads the configuration from the config file
func ReadConfigFromFile() (config *Config, err error) {
- filePath, err := ConfigPath()
+ filePath, err := Path()
if err != nil {
return
}
diff --git a/pkg/model/button/button.go b/pkg/model/button/button.go
index 14ac65b..59adab3 100644
--- a/pkg/model/button/button.go
+++ b/pkg/model/button/button.go
@@ -1,3 +1,18 @@
+// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
package button
import (
@@ -7,8 +22,10 @@ import (
"github.com/charmbracelet/lipgloss"
)
+// Pressed is a flag that is enabled when the button is pressed.
type Pressed bool
+// Model is the model for a button.
type Model struct {
text string
FocusStyle lipgloss.Style
@@ -17,6 +34,7 @@ type Model struct {
Invalid bool
}
+// New returns a new button model.
func New(text string) Model {
return Model{
text: text,
@@ -25,23 +43,28 @@ func New(text string) Model {
}
}
+// Focus sets the focus flag to true.
func (m *Model) Focus() tea.Cmd {
m.focus = true
return nil
}
+// Blur sets the focus flag to false.
func (m *Model) Blur() {
m.focus = false
}
+// Focused returns true if the button is focused.
func (m *Model) Focused() bool {
return m.focus
}
+// Init initializes the button.
func (m Model) Init() tea.Cmd {
return nil
}
+// Update updates the button.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
return m, nil
@@ -53,9 +76,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
case tea.KeyEnter:
if m.Invalid {
return m, nil
- } else {
- return m, func() tea.Msg { return Pressed(true) }
}
+ return m, func() tea.Msg { return Pressed(true) }
default:
return m, nil
}
@@ -64,6 +86,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
+// View renders the button.
func (m Model) View() string {
var b strings.Builder
var text string
diff --git a/pkg/model/credential/credential.go b/pkg/model/credential/credential.go
index 17f0cc2..a601a2e 100644
--- a/pkg/model/credential/credential.go
+++ b/pkg/model/credential/credential.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -26,15 +25,16 @@ import (
"github.com/charmbracelet/lipgloss"
)
+// Default Style for this widget
var (
- FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
- FocusSecondry = lipgloss.AdaptiveColor{Light: "18", Dark: "220"}
+ FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
+ FocusSecondary = lipgloss.AdaptiveColor{Light: "18", Dark: "220"}
- StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
- StandardSecondry = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
+ StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
+ StandardSecondary = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
focusedStyle = lipgloss.NewStyle().Foreground(FocusPrimary)
- blurredStyle = lipgloss.NewStyle().Foreground(StandardSecondry)
+ blurredStyle = lipgloss.NewStyle().Foreground(StandardSecondary)
noStyle = lipgloss.NewStyle()
)
@@ -47,9 +47,8 @@ type Model struct {
func (m *Model) Values() (string, string) {
if validInputs(&m.inputs) {
return m.inputs[0].Value(), m.inputs[1].Value()
- } else {
- return "", ""
}
+ return "", ""
}
func validInputs(inputs *[]textinput.Model) bool {
diff --git a/pkg/model/datetime/datetime.go b/pkg/model/datetime/datetime.go
index e962f7c..2860ad3 100644
--- a/pkg/model/datetime/datetime.go
+++ b/pkg/model/datetime/datetime.go
@@ -9,28 +9,34 @@ import (
tea "github.com/charmbracelet/bubbletea"
)
+// Model is the model for the datetime component
type Model struct {
time time.Time
input textinput.Model
}
+// Value returns the current value of the datetime component
func (m *Model) Value() string {
return m.time.Format(time.RFC3339)
}
+// ValueUtc returns the current value of the datetime component in UTC
func (m *Model) ValueUtc() string {
return m.time.UTC().Format(time.RFC3339)
}
+// SetTime sets the value of the datetime component
func (m *Model) SetTime(t time.Time) {
m.time = t
m.input.SetValue(m.time.Format(time.DateTime))
}
+// Time returns the current time of the datetime component
func (m *Model) Time() time.Time {
return m.time
}
+// New creates a new datetime component
func New(prompt string) Model {
input := textinput.New()
input.Width = 20
@@ -42,23 +48,28 @@ func New(prompt string) Model {
}
}
+// Focus focuses the datetime component
func (m *Model) Focus() tea.Cmd {
m.input.Focus()
return nil
}
+// Blur blurs the datetime component
func (m *Model) Blur() {
m.input.Blur()
}
+// Focused returns true if the datetime component is focused
func (m *Model) Focused() bool {
return m.input.Focused()
}
+// Init initializes the datetime component
func (m Model) Init() tea.Cmd {
return nil
}
+// Update updates the datetime component
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmd tea.Cmd
if !m.Focused() {
@@ -96,6 +107,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
+// View returns the view of the datetime component
func (m Model) View() string {
return m.input.View()
}
diff --git a/pkg/model/defaultprofile/profile.go b/pkg/model/defaultprofile/profile.go
index 379a9bb..0106806 100644
--- a/pkg/model/defaultprofile/profile.go
+++ b/pkg/model/defaultprofile/profile.go
@@ -1,7 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
-//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
@@ -28,18 +26,21 @@ import (
)
var (
- FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
+ // FocusPrimary is the primary focus color
+ FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
+ // FocusSecondry is the secondry focus color
FocusSecondry = lipgloss.AdaptiveColor{Light: "18", Dark: "220"}
-
- StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
- StandardSecondry = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
+ // StandardPrimary is the primary standard color
+ StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
+ // StandardSecondary is the secondary standard color
+ StandardSecondary = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
focusTitleStyle = lipgloss.NewStyle().Foreground(FocusPrimary)
focusDescStyle = lipgloss.NewStyle().Foreground(FocusSecondry)
focusedOuterStyle = lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).BorderLeft(true).BorderForeground(FocusPrimary)
standardTitleStyle = lipgloss.NewStyle().Foreground(StandardPrimary)
- standardDescStyle = lipgloss.NewStyle().Foreground(StandardSecondry)
+ standardDescStyle = lipgloss.NewStyle().Foreground(StandardSecondary)
)
type item struct {
@@ -81,6 +82,7 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list
fmt.Fprint(w, render)
}
+// Model for profile selection command
type Model struct {
list list.Model
Choice string
@@ -92,7 +94,7 @@ func New(profiles map[string]config.Profile) Model {
for name, profile := range profiles {
i := item{
title: name,
- url: profile.Url,
+ url: profile.URL,
user: profile.Username,
}
items = append(items, i)
diff --git a/pkg/model/query.go b/pkg/model/query.go
index 36f8649..82d417a 100644
--- a/pkg/model/query.go
+++ b/pkg/model/query.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -24,9 +23,8 @@ import (
"math"
"net/http"
"os"
- "time"
-
"pb/pkg/config"
+ "time"
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
@@ -45,12 +43,13 @@ const (
metadataKey = "p_metadata"
)
+// Style for this widget
var (
FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
FocusSecondry = lipgloss.AdaptiveColor{Light: "18", Dark: "220"}
- StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
- StandardSecondry = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
+ StandardPrimary = lipgloss.AdaptiveColor{Light: "235", Dark: "255"}
+ StandardSecondary = lipgloss.AdaptiveColor{Light: "238", Dark: "254"}
borderedStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder(), true).
@@ -66,7 +65,9 @@ var (
baseBoldUnderlinedStyle = lipgloss.NewStyle().BorderForeground(StandardPrimary).Bold(true)
headerStyle = lipgloss.NewStyle().Inherit(baseStyle).Foreground(FocusSecondry).Bold(true)
tableStyle = lipgloss.NewStyle().Inherit(baseStyle).Align(lipgloss.Left)
+)
+var (
customBorder = table.Border{
Top: "─",
Left: "│",
@@ -94,8 +95,10 @@ var (
QueryNavigationMap = []string{"query", "time", "table"}
)
-type Mode int
-type FetchResult int
+type (
+ Mode int
+ FetchResult int
+)
type FetchData struct {
status FetchResult
@@ -104,13 +107,13 @@ type FetchData struct {
}
const (
- FetchOk FetchResult = iota
- FetchErr
+ fetchOk FetchResult = iota
+ fetchErr
)
const (
- OverlayNone uint = iota
- OverlayInputs
+ overlayNone uint = iota
+ overlayInputs
)
type QueryModel struct {
@@ -118,7 +121,7 @@ type QueryModel struct {
height int
table table.Model
query textarea.Model
- timerange TimeInputModel
+ timeRange TimeInputModel
profile config.Profile
help help.Model
status StatusBar
@@ -164,7 +167,7 @@ func NewQueryModel(profile config.Profile, stream string, duration uint) QueryMo
WithPageSize(30).
WithBaseStyle(tableStyle).
WithMissingDataIndicatorStyled(table.StyledCell{
- Style: lipgloss.NewStyle().Foreground(StandardSecondry),
+ Style: lipgloss.NewStyle().Foreground(StandardSecondary),
Data: "╌",
}).WithMaxTotalWidth(100)
@@ -186,17 +189,17 @@ func NewQueryModel(profile config.Profile, stream string, duration uint) QueryMo
height: h,
table: table,
query: query,
- timerange: inputs,
- overlay: OverlayNone,
+ timeRange: inputs,
+ overlay: overlayNone,
profile: profile,
help: help,
- status: NewStatusBar(profile.Url, stream, w),
+ status: NewStatusBar(profile.URL, stream, w),
}
}
func (m QueryModel) Init() tea.Cmd {
// Just return `nil`, which means "no I/O right now, please."
- return NewFetchTask(m.profile, m.query.Value(), m.timerange.StartValueUtc(), m.timerange.EndValueUtc())
+ return NewFetchTask(m.profile, m.query.Value(), m.timeRange.StartValueUtc(), m.timeRange.EndValueUtc())
}
func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -215,7 +218,7 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case FetchData:
- if msg.status == FetchOk {
+ if msg.status == fetchOk {
m.UpdateTable(msg)
} else {
m.status.Error = "failed to query"
@@ -224,15 +227,15 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Is it a key press?
case tea.KeyMsg:
- // special behaviour on main page
- if m.overlay == OverlayNone {
+ // special behavior on main page
+ if m.overlay == overlayNone {
if msg.Type == tea.KeyEnter && m.currentFocus() == "time" {
- m.overlay = OverlayInputs
+ m.overlay = overlayInputs
return m, nil
}
if msg.Type == tea.KeyTab {
- m.focused += 1
+ m.focused++
if m.focused > len(QueryNavigationMap)-1 {
m.focused = 0
}
@@ -241,10 +244,10 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
- // special behaviour on time input page
- if m.overlay == OverlayInputs {
+ // special behavior on time input page
+ if m.overlay == overlayInputs {
if msg.Type == tea.KeyEnter {
- m.overlay = OverlayNone
+ m.overlay = overlayNone
m.focusSelected()
return m, nil
}
@@ -252,8 +255,8 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// common keybind
if msg.Type == tea.KeyCtrlR {
- m.overlay = OverlayNone
- return m, NewFetchTask(m.profile, m.query.Value(), m.timerange.StartValueUtc(), m.timerange.EndValueUtc())
+ m.overlay = overlayNone
+ return m, NewFetchTask(m.profile, m.query.Value(), m.timeRange.StartValueUtc(), m.timeRange.EndValueUtc())
}
switch msg.Type {
@@ -262,7 +265,7 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Quit
default:
switch m.overlay {
- case OverlayNone:
+ case overlayNone:
switch m.currentFocus() {
case "query":
m.query, cmd = m.query.Update(msg)
@@ -270,8 +273,8 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.table, cmd = m.table.Update(msg)
}
cmds = append(cmds, cmd)
- case OverlayInputs:
- m.timerange, cmd = m.timerange.Update(msg)
+ case overlayInputs:
+ m.timeRange, cmd = m.timeRange.Update(msg)
cmds = append(cmds, cmd)
}
}
@@ -280,7 +283,7 @@ func (m QueryModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
func (m QueryModel) View() string {
- var outer = lipgloss.NewStyle().Inherit(baseStyle).
+ outer := lipgloss.NewStyle().Inherit(baseStyle).
UnsetMaxHeight().Width(m.width).Height(m.height)
m.table = m.table.WithMaxTotalWidth(m.width - 2)
@@ -294,8 +297,8 @@ func (m QueryModel) View() string {
time := lipgloss.JoinVertical(
lipgloss.Left,
- fmt.Sprintf("%s %s ", baseBoldUnderlinedStyle.Render(" start "), m.timerange.start.Value()),
- fmt.Sprintf("%s %s ", baseBoldUnderlinedStyle.Render(" end "), m.timerange.end.Value()),
+ fmt.Sprintf("%s %s ", baseBoldUnderlinedStyle.Render(" start "), m.timeRange.start.Value()),
+ fmt.Sprintf("%s %s ", baseBoldUnderlinedStyle.Render(" end "), m.timeRange.end.Value()),
)
queryOuter, timeOuter := &borderedStyle, &borderedStyle
@@ -312,7 +315,7 @@ func (m QueryModel) View() string {
}
switch m.overlay {
- case OverlayNone:
+ case overlayNone:
mainView = lipgloss.JoinVertical(lipgloss.Left,
lipgloss.JoinHorizontal(lipgloss.Top, queryOuter.Render(m.query.View()), timeOuter.Render(time)),
tableOuter.Render(m.table.View()),
@@ -322,14 +325,14 @@ func (m QueryModel) View() string {
helpKeys = TextAreaHelpKeys{}.FullHelp()
case "time":
helpKeys = [][]key.Binding{
- {key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select timerange"))},
+ {key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "select timeRange"))},
}
case "table":
helpKeys = tableHelpBinds.FullHelp()
}
- case OverlayInputs:
- mainView = m.timerange.View()
- helpKeys = m.timerange.FullHelp()
+ case overlayInputs:
+ mainView = m.timeRange.View()
+ helpKeys = m.timeRange.FullHelp()
}
helpKeys = append(helpKeys, additionalKeyBinds)
helpView = m.help.FullHelpView(helpKeys)
@@ -350,10 +353,10 @@ type QueryData struct {
Records []map[string]interface{} `json:"records"`
}
-func NewFetchTask(profile config.Profile, query string, start_time string, end_time string) func() tea.Msg {
+func NewFetchTask(profile config.Profile, query string, startTime string, endTime string) func() tea.Msg {
return func() tea.Msg {
res := FetchData{
- status: FetchErr,
+ status: fetchErr,
schema: []string{},
data: []map[string]interface{}{},
}
@@ -362,35 +365,33 @@ func NewFetchTask(profile config.Profile, query string, start_time string, end_t
Timeout: time.Second * 50,
}
- data, status := fetchData(client, &profile, query, start_time, end_time)
- if status == FetchErr {
- return res
- } else {
+ data, status := fetchData(client, &profile, query, startTime, endTime)
+
+ if status == fetchOk {
res.data = data.Records
res.schema = data.Fields
+ res.status = fetchOk
}
- res.status = FetchOk
-
return res
}
}
-func fetchData(client *http.Client, profile *config.Profile, query string, start_time string, end_time string) (data QueryData, res FetchResult) {
+func fetchData(client *http.Client, profile *config.Profile, query string, startTime string, endTime string) (data QueryData, res FetchResult) {
data = QueryData{}
- res = FetchErr
+ res = fetchErr
- query_template := `{
+ queryTemplate := `{
"query": "%s",
"startTime": "%s",
"endTime": "%s"
}
`
- final_query := fmt.Sprintf(query_template, query, start_time, end_time)
+ finalQuery := fmt.Sprintf(queryTemplate, query, startTime, endTime)
- endpoint := fmt.Sprintf("%s/%s", profile.Url, "api/v1/query?fields=true")
- req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer([]byte(final_query)))
+ endpoint := fmt.Sprintf("%s/%s", profile.URL, "api/v1/query?fields=true")
+ req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer([]byte(finalQuery)))
if err != nil {
return
}
@@ -407,28 +408,28 @@ func fetchData(client *http.Client, profile *config.Profile, query string, start
return
}
- res = FetchOk
+ res = fetchOk
return
}
func (m *QueryModel) UpdateTable(data FetchData) {
// pin p_timestamp to left if available
- contains_timestamp := slices.Contains(data.schema, datetimeKey)
- contains_tags := slices.Contains(data.schema, tagKey)
- contains_metadata := slices.Contains(data.schema, metadataKey)
+ containsTimestamp := slices.Contains(data.schema, datetimeKey)
+ containsTags := slices.Contains(data.schema, tagKey)
+ containsMetadata := slices.Contains(data.schema, metadataKey)
columns := make([]table.Column, len(data.schema))
columnIndex := 0
- if contains_timestamp {
+ if containsTimestamp {
columns[0] = table.NewColumn(datetimeKey, datetimeKey, datetimeWidth)
- columnIndex += 1
+ columnIndex++
}
- if contains_tags {
+ if containsTags {
columns[len(columns)-2] = table.NewColumn(tagKey, tagKey, inferWidthForColumns(tagKey, &data.data, 100, 80)).WithFiltered(true)
}
- if contains_metadata {
+ if containsMetadata {
columns[len(columns)-1] = table.NewColumn(metadataKey, metadataKey, inferWidthForColumns(metadataKey, &data.data, 100, 80)).WithFiltered(true)
}
@@ -439,28 +440,28 @@ func (m *QueryModel) UpdateTable(data FetchData) {
default:
width := inferWidthForColumns(title, &data.data, 100, 100) + 1
columns[columnIndex] = table.NewColumn(title, title, width).WithFiltered(true)
- columnIndex += 1
+ columnIndex++
}
}
rows := make([]table.Row, len(data.data))
for i := 0; i < len(data.data); i++ {
- row_json := data.data[i]
- rows[i] = table.NewRow(row_json)
+ rowJSON := data.data[i]
+ rows[i] = table.NewRow(rowJSON)
}
m.table = m.table.WithColumns(columns)
m.table = m.table.WithRows(rows)
}
-func inferWidthForColumns(column string, data *[]map[string]interface{}, max_records int, max_width int) (width int) {
+func inferWidthForColumns(column string, data *[]map[string]interface{}, maxRecords int, maxWidth int) (width int) {
width = 2
records := 0
- if len(*data) < max_records {
+ if len(*data) < maxRecords {
records = len(*data)
} else {
- records = max_records
+ records = maxRecords
}
for i := 0; i < records; i++ {
@@ -476,10 +477,10 @@ func inferWidthForColumns(column string, data *[]map[string]interface{}, max_rec
}
if w > width {
- if w < max_width {
+ if w < maxWidth {
width = w
} else {
- width = max_width
+ width = maxWidth
return
}
}
diff --git a/pkg/model/role/role.go b/pkg/model/role/role.go
index 3ed6b2c..332e4ff 100644
--- a/pkg/model/role/role.go
+++ b/pkg/model/role/role.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -34,7 +33,10 @@ var (
navigationMapStream = []string{"role", "stream", "button"}
navigationMap = []string{"role", "button"}
navigationMapNone = []string{"role"}
+)
+// Style for role selection widget
+var (
FocusPrimary = lipgloss.AdaptiveColor{Light: "16", Dark: "226"}
FocusSecondry = lipgloss.AdaptiveColor{Light: "18", Dark: "220"}
@@ -151,13 +153,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyCtrlC:
return m, tea.Quit
case tea.KeyDown, tea.KeyTab, tea.KeyEnter:
- m.focusIndex += 1
+ m.focusIndex++
if m.focusIndex >= len(*m.navMap) {
m.focusIndex = 0
}
m.FocusSelected()
case tea.KeyUp, tea.KeyShiftTab:
- m.focusIndex -= 1
+ m.focusIndex--
if m.focusIndex < 0 {
m.focusIndex = len(*m.navMap) - 1
}
diff --git a/pkg/model/selection/selection.go b/pkg/model/selection/selection.go
index 37e15f2..cad26df 100644
--- a/pkg/model/selection/selection.go
+++ b/pkg/model/selection/selection.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -22,6 +21,7 @@ import (
"github.com/charmbracelet/lipgloss"
)
+// Model is the model for the selection component
type Model struct {
items []string
focusIndex int
@@ -30,23 +30,28 @@ type Model struct {
BlurredStyle lipgloss.Style
}
+// Focus focuses the selection component
func (m *Model) Focus() tea.Cmd {
m.focus = true
return nil
}
+// Blur blurs the selection component
func (m *Model) Blur() {
m.focus = false
}
+// Focused returns true if the selection component is focused
func (m *Model) Focused() bool {
return m.focus
}
+// Value returns the value of the selection component
func (m *Model) Value() string {
return m.items[m.focusIndex]
}
+// New creates a new selection component
func New(items []string) Model {
m := Model{
focusIndex: 0,
@@ -57,10 +62,12 @@ func New(items []string) Model {
return m
}
+// Init initializes the selection component
func (m Model) Init() tea.Cmd {
return nil
}
+// Update updates the selection component
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
if !m.focus {
return m, nil
@@ -71,11 +78,11 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
switch msg.Type {
case tea.KeyLeft:
if m.focusIndex > 0 {
- m.focusIndex -= 1
+ m.focusIndex--
}
case tea.KeyRight:
if m.focusIndex < len(m.items)-1 {
- m.focusIndex += 1
+ m.focusIndex++
}
}
}
@@ -83,6 +90,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
return m, nil
}
+// View renders the selection component
func (m Model) View() string {
render := make([]string, len(m.items))
diff --git a/pkg/model/status.go b/pkg/model/status.go
index 93e5793..ec37e40 100644
--- a/pkg/model/status.go
+++ b/pkg/model/status.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -70,28 +69,28 @@ func (m StatusBar) Init() tea.Cmd {
return nil
}
-func (m StatusBar) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
+func (m StatusBar) Update(_ tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
func (m StatusBar) View() string {
var right string
- var right_style lipgloss.Style
+ var rightStyle lipgloss.Style
if m.Error != "" {
right = m.Error
- right_style = errorStyle
+ rightStyle = errorStyle
} else {
right = m.Info
- right_style = infoStyle
+ rightStyle = infoStyle
}
left := lipgloss.JoinHorizontal(lipgloss.Bottom, titleStyle.Render(m.title), hostStyle.Render(m.host), streamStyle.Render(m.stream))
- left_width := lipgloss.Width(left)
- right_width := m.width - left_width
+ leftWidth := lipgloss.Width(left)
+ rightWidth := m.width - leftWidth
- right = right_style.Width(right_width).Render(right)
+ right = rightStyle.Width(rightWidth).Render(right)
return lipgloss.JoinHorizontal(lipgloss.Bottom, left, right)
}
diff --git a/pkg/model/timeinput.go b/pkg/model/timeinput.go
index b687eb9..e68629b 100644
--- a/pkg/model/timeinput.go
+++ b/pkg/model/timeinput.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -32,23 +31,23 @@ var rangeNavigationMap = []string{
"list", "start", "end",
}
-type EndTimeKeyBind struct {
+type endTimeKeyBind struct {
ResetTime key.Binding
Ok key.Binding
}
-func (k EndTimeKeyBind) ShortHelp() []key.Binding {
+func (k endTimeKeyBind) ShortHelp() []key.Binding {
return []key.Binding{k.ResetTime, k.Ok}
}
-func (k EndTimeKeyBind) FullHelp() [][]key.Binding {
+func (k endTimeKeyBind) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.ResetTime},
{k.Ok},
}
}
-var EndHelpBinds = EndTimeKeyBind{
+var endHelpBinds = endTimeKeyBind{
ResetTime: key.NewBinding(
key.WithKeys("ctrl+{"),
key.WithHelp("ctrl+{", "change end time to current time"),
@@ -100,12 +99,12 @@ func (m *TimeInputModel) Navigate(key tea.KeyMsg) {
if m.focus == 0 {
m.focus = len(rangeNavigationMap)
}
- m.focus -= 1
+ m.focus--
case "tab":
if m.focus == len(rangeNavigationMap)-1 {
m.focus = -1
}
- m.focus += 1
+ m.focus++
default:
return
}
@@ -115,6 +114,7 @@ func (m *TimeInputModel) currentFocus() string {
return rangeNavigationMap[m.focus]
}
+// NewTimeInputModel creates a new model
func NewTimeInputModel(duration uint) TimeInputModel {
endTime := time.Now()
startTime := endTime.Add(TenMinute)
@@ -124,12 +124,12 @@ func NewTimeInputModel(duration uint) TimeInputModel {
}
list := NewTimeRangeModel()
- input_style := lipgloss.NewStyle().Inherit(baseStyle).Bold(true).Width(6).Align(lipgloss.Center)
+ inputStyle := lipgloss.NewStyle().Inherit(baseStyle).Bold(true).Width(6).Align(lipgloss.Center)
- start := datetime.New(input_style.Render("start"))
+ start := datetime.New(inputStyle.Render("start"))
start.SetTime(startTime)
start.Focus()
- end := datetime.New(input_style.Render("end"))
+ end := datetime.New(inputStyle.Render("end"))
end.SetTime(endTime)
return TimeInputModel{
@@ -141,7 +141,7 @@ func NewTimeInputModel(duration uint) TimeInputModel {
}
func (m TimeInputModel) FullHelp() [][]key.Binding {
- return EndHelpBinds.FullHelp()
+ return endHelpBinds.FullHelp()
}
func (m TimeInputModel) Init() tea.Cmd {
diff --git a/pkg/model/timerange.go b/pkg/model/timerange.go
index 83a25a9..2fb7e70 100644
--- a/pkg/model/timerange.go
+++ b/pkg/model/timerange.go
@@ -1,6 +1,5 @@
// Copyright (c) 2023 Cloudnatively Services Pvt Ltd
//
-// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
@@ -28,6 +27,7 @@ import (
"github.com/charmbracelet/lipgloss"
)
+// Items for time range
const (
TenMinute = -10 * time.Minute
TwentyMinute = -20 * time.Minute
@@ -51,7 +51,7 @@ var (
timeDurationItem{duration: OneWeek, repr: "1 Week"},
}
- listItemRender = lipgloss.NewStyle().Foreground(StandardSecondry)
+ listItemRender = lipgloss.NewStyle().Foreground(StandardSecondary)
listSelectedItemRender = lipgloss.NewStyle().Foreground(FocusPrimary)
)
@@ -83,6 +83,7 @@ func (d timeDurationItemDelegate) Render(w io.Writer, m list.Model, index int, l
fmt.Fprint(w, fn(i.repr))
}
+// NewTimeRangeModel creates new range model
func NewTimeRangeModel() list.Model {
list := list.New(timeDurations, timeDurationItemDelegate{}, 20, 10)
list.SetShowPagination(false)