Skip to content

Commit

Permalink
add grlx init tui
Browse files Browse the repository at this point in the history
  • Loading branch information
taigrr committed Oct 21, 2023
1 parent 88be916 commit 5e4bc15
Show file tree
Hide file tree
Showing 5 changed files with 257 additions and 12 deletions.
193 changes: 192 additions & 1 deletion cmd/grlx/cmd/init.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,208 @@
package cmd

import (
"fmt"
"log"
"os"
"strings"

"github.com/charmbracelet/bubbles/cursor"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/gogrlx/grlx/auth"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var (
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#00ffef"))
blurredStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ced4da"))
cursorStyle = focusedStyle.Copy()
noStyle = lipgloss.NewStyle()
helpStyle = blurredStyle.Copy()
cursorModeHelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#ced4da"))

focusedButton = focusedStyle.Copy().Render("[ Submit ]")
blurredButton = fmt.Sprintf("[ %s ]", blurredStyle.Render("Submit"))
)

var initCmd = &cobra.Command{
Use: "init",
Short: "get started with a new grlx installation",
Run: func(cmd *cobra.Command, _ []string) {
cmd.Help()
auth.CreatePrivkey()
pubKey, err := auth.GetPubkey()
if err != nil {
log.Println("Error: " + err.Error())
os.Exit(1)
}
model, err := tea.NewProgram(initialModel()).Run()
if err == nil {
mData := model.(configModel)
fInterface := mData.inputs[0].Value()
fAPIPort := mData.inputs[1].Value()
fBusPort := mData.inputs[2].Value()
if fInterface != "" {
viper.Set("FarmerInterface", fInterface)
}
if fAPIPort != "" {
viper.Set("FarmerAPIPort", fAPIPort)
}
if fBusPort != "" {
viper.Set("FarmerBusPort", fBusPort)
}
viper.WriteConfig()
fmt.Printf("Public key: %s\n", pubKey)
} else {
fmt.Printf("Error: opening the configuration interface. Please manually edit %s\n", viper.ConfigFileUsed())
fmt.Printf("Public key: %s\n", pubKey)
os.Exit(1)
}
},
}

type configModel struct {
focusIndex int
inputs []textinput.Model
farmerAPIPort int
farmerBusPort int
farmerInterface string
cursorMode cursor.Mode
}

func initialModel() configModel {
m := configModel{
inputs: make([]textinput.Model, 3),
}

var t textinput.Model
for i := range m.inputs {
t = textinput.New()
t.Cursor.Style = cursorStyle
t.CharLimit = 64

switch i {
case 0:
t.Placeholder = "Farmer Interface"
t.Focus()
t.PromptStyle = focusedStyle
t.TextStyle = focusedStyle
case 1:
t.Placeholder = "Farmer API Port (default: 5405)"
t.CharLimit = 5
case 2:
t.Placeholder = "Farmer Bus Port (default: 5406)"
t.CharLimit = 5
}
m.inputs[i] = t
}

return m
}

func (m configModel) Init() tea.Cmd {
return textinput.Blink
}

func (m configModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "ctrl+c", "esc":
return m, tea.Quit

// Change cursor mode
case "ctrl+r":
m.cursorMode++
if m.cursorMode > cursor.CursorHide {
m.cursorMode = cursor.CursorBlink
}
cmds := make([]tea.Cmd, len(m.inputs))
for i := range m.inputs {
cmds[i] = m.inputs[i].Cursor.SetMode(m.cursorMode)
}
return m, tea.Batch(cmds...)

// Set focus to next input
case "tab", "shift+tab", "enter", "up", "down":
s := msg.String()

// Did the user press enter while the submit button was focused?
// If so, exit.
if s == "enter" && m.focusIndex == len(m.inputs) {
return m, tea.Quit
}

// Cycle indexes
if s == "up" || s == "shift+tab" {
m.focusIndex--
} else {
m.focusIndex++
}

if m.focusIndex > len(m.inputs) {
m.focusIndex = 0
} else if m.focusIndex < 0 {
m.focusIndex = len(m.inputs)
}

cmds := make([]tea.Cmd, len(m.inputs))
for i := 0; i <= len(m.inputs)-1; i++ {
if i == m.focusIndex {
// Set focused state
cmds[i] = m.inputs[i].Focus()
m.inputs[i].PromptStyle = focusedStyle
m.inputs[i].TextStyle = focusedStyle
continue
}
// Remove focused state
m.inputs[i].Blur()
m.inputs[i].PromptStyle = noStyle
m.inputs[i].TextStyle = noStyle
}

return m, tea.Batch(cmds...)
}
}

// Handle character input and blinking
cmd := m.updateInputs(msg)

return m, cmd
}

func (m *configModel) updateInputs(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, len(m.inputs))

// Only text inputs with Focus() set will respond, so it's safe to simply
// update all of them here without any further logic.
for i := range m.inputs {
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
}

return tea.Batch(cmds...)
}

func (m configModel) View() string {
var b strings.Builder
b.WriteString("\nWelcome to " + focusedStyle.Render("grlx") + "! Update your config defaults below.\n\n")
for i := range m.inputs {
b.WriteString(m.inputs[i].View())
if i < len(m.inputs)-1 {
b.WriteRune('\n')
}
}

button := &blurredButton
if m.focusIndex == len(m.inputs) {
button = &focusedButton
}
fmt.Fprintf(&b, "\n\n%s\n\n", *button)

return b.String()
}

func init() {
rootCmd.AddCommand(initCmd)
}
23 changes: 13 additions & 10 deletions cmd/grlx/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,31 +60,34 @@ func init() {
}
}
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/grlx/grlx)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
if !pki.RootCACached("grlx") {
noFailForCert := false
if len(os.Args) > 1 {
noFailForCert = os.Args[1] == "version" || os.Args[1] == "help" || os.Args[1] == "auth" || os.Args[1] == "init"
}
isInit := false
if len(os.Args) > 1 {
isInit = os.Args[1] == "init"
}
if !pki.RootCACached("grlx") && !isInit {
fmt.Print("The TLS certificate for this farmer is unknown. Would you like to download and trust it? ")
shouldDownload, err := util.UserConfirmWithDefault(true)
for err != nil {
shouldDownload, err = util.UserConfirmWithDefault(true)
}
if !shouldDownload {
if !shouldDownload && !noFailForCert {
fmt.Println("No certificate, exiting!")
os.Exit(1)
}
}
err := pki.LoadRootCA("grlx")
isVersionOrHelp := false
if len(os.Args) > 1 {
isVersionOrHelp = os.Args[1] == "version" || os.Args[1] == "help"
}
if err != nil && !isVersionOrHelp {

if err != nil && !noFailForCert {
fmt.Printf("error: %v\n", err)
color.Red("The RootCA could not be loaded from %s. Exiting!", config.GrlxRootCA)
os.Exit(1)
}
err = client.CreateSecureTransport()
if err != nil && !isVersionOrHelp {
if err != nil && !noFailForCert {
if os.Args[1] != "version" {
fmt.Printf("error: %v\n", err)
color.Red("The API client could not be created. Exiting!")
Expand Down
2 changes: 1 addition & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func LoadConfig(binary string) {
switch binary {
case "grlx":
dirname, errHomeDir := os.UserHomeDir()
if err != nil {
if errHomeDir != nil {
log.Fatal(errHomeDir)
}
cfgPath := filepath.Join(dirname, ".config/grlx/")
Expand Down
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ module github.com/gogrlx/grlx
go 1.21.3

require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.9.1
github.com/djherbis/atime v1.1.0
github.com/fatih/color v1.15.0
github.com/google/uuid v1.3.1
Expand All @@ -18,19 +21,30 @@ require (
)

require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/klauspost/compress v1.17.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.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/muesli/termenv v0.15.2 // indirect
github.com/nats-io/jwt/v2 v2.5.2 // indirect
github.com/nats-io/nuid v1.0.1 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
Expand All @@ -41,7 +55,9 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
Expand Down
Loading

0 comments on commit 5e4bc15

Please sign in to comment.