Skip to content

Commit

Permalink
Feature: TUI Config (#161)
Browse files Browse the repository at this point in the history
* tui config

* update readme

* tui config logging and comment updates

* accessibility color overrides

* address comments

* added comment
  • Loading branch information
sambukowski authored Aug 21, 2024
1 parent 0166828 commit 0932ce5
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 63 deletions.
23 changes: 19 additions & 4 deletions modules/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,11 +189,26 @@ configs and binaries for running the Astria stack.
## Configuration
The CLI uses three configuration files:
The CLI uses the following configuration files:
1. `base-config.toml`: Sets service environment variables
2. `networks-config.toml`: Configures services and sequencer networks
3. `sequencer-networks-config.toml`: Used for `astria-go sequencer` commands
1. `tui-config.toml`: Controls app start state of the devrunner TUI
2. `base-config.toml`: Sets service environment variables
3. `networks-config.toml`: Configures services and sequencer networks
4. `sequencer-networks-config.toml`: Used for `astria-go sequencer` commands
### Set TUI App Start State
Once `astria-go dev init` has been run, edit `~/.astria/tui-config.toml` to
control the starting settings of the TUI app.
The `highlight_color` and `border_color` accept both [W3C named
colors](https://www.w3schools.com/tags/ref_colornames.asp) and hexadecimal
notation:
```toml
highlight_color = "deepskyblue" # names should be all lowercase with no spaces
border_color = "#808080"
```
### Set Service Environment Variables
Expand Down
23 changes: 23 additions & 0 deletions modules/cli/cmd/cliflaghandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,26 @@ func (f *CliFlagHandler) GetValue(flagName string) string {
log.Debugf("%s flag is not set, using default: %s", flagName, value)
return value
}

// GetChanged returns a boolean indicating if the flag was changed.
func (f *CliFlagHandler) GetChanged(flagName string) bool {
// confirm the flag exists
flag := f.Cmd.Flags().Lookup(flagName)
if flag == nil {
log.Errorf("Flag '%s' doesn't exist. Has it been bound?", flagName)
panic(fmt.Sprintf("getValue: flag doesn't exist: %s", flagName))
}

// check if the flag was changed via a config override
if f.useConfigFlag != "" && f.Cmd.Flag(f.useConfigFlag).Changed {
// check if value exists in config
if f.config != nil {
_, found := GetFieldValueByTag(f.config, "flag", flagName)
if found {
return true
}
}
}

return f.Cmd.Flag(flagName).Changed
}
3 changes: 3 additions & 0 deletions modules/cli/cmd/devrunner/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
DefaultServiceLogLevel = "info"
DefaultTargetNetwork = "local"
LocalNativeDenom = "nria"
DefaultTUIConfigName = "tui-config.toml"
DefaultHighlightColor = "blue"
DefaultBorderColor = "gray"

// NOTE - do not include the 'v' at the beginning of the version number
CometbftVersion = "0.38.8"
Expand Down
115 changes: 115 additions & 0 deletions modules/cli/cmd/devrunner/config/tui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package config

import (
"fmt"
"os"

"github.com/pelletier/go-toml/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)

// TUIConfig is the struct that holds the configuration and start state for the
// TUI.
type TUIConfig struct {
// Log viewer settings for services
AutoScroll bool `mapstructure:"auto_scroll" toml:"auto_scroll"`
WrapLines bool `mapstructure:"wrap_lines" toml:"wrap_lines"`
Borderless bool `mapstructure:"borderless" toml:"borderless"`

// Override value for the Default Instance Name
OverrideInstanceName string `mapstructure:"override_instance_name" toml:"override_instance_name"`

// Known services start minimized
CometBFTStartsMinimized bool `mapstructure:"cometbft_starts_minimized" toml:"cometbft_starts_minimized"`
ConductorStartsMinimized bool `mapstructure:"conductor_starts_minimized" toml:"conductor_starts_minimized"`
ComposerStartsMinimized bool `mapstructure:"composer_starts_minimized" toml:"composer_starts_minimized"`
SequencerStartsMinimized bool `mapstructure:"sequencer_starts_minimized" toml:"sequencer_starts_minimized"`
// Generic services start minimized
GenericStartsMinimized bool `mapstructure:"generic_starts_minimized" toml:"generic_starts_minimized"`

// Accessibility settings
HighlightColor string `mapstructure:"highlight_color" toml:"highlight_color"`
BorderColor string `mapstructure:"border_color" toml:"border_color"`
}

// DefaultTUIConfig returns a TUIConfig struct populated with all default
// values.
func DefaultTUIConfig() TUIConfig {
return TUIConfig{
AutoScroll: true,
WrapLines: false,
Borderless: false,
OverrideInstanceName: DefaultInstanceName,
CometBFTStartsMinimized: false,
ConductorStartsMinimized: false,
ComposerStartsMinimized: false,
SequencerStartsMinimized: false,
GenericStartsMinimized: true,
HighlightColor: DefaultHighlightColor,
BorderColor: DefaultBorderColor,
}
}

// String returns a string representation of the TUIConfig struct.
func (c TUIConfig) String() string {
output := "TUI Config: {"
output += fmt.Sprintf("AutoScroll: %v, ", c.AutoScroll)
output += fmt.Sprintf("WrapLines: %v, ", c.WrapLines)
output += fmt.Sprintf("Borderless: %v, ", c.Borderless)
output += fmt.Sprintf("OverrideInstanceName: %s, ", c.OverrideInstanceName)
output += fmt.Sprintf("CometBFTStartsMinimized: %v, ", c.CometBFTStartsMinimized)
output += fmt.Sprintf("ConductorStartsMinimized: %v, ", c.ConductorStartsMinimized)
output += fmt.Sprintf("ComposerStartsMinimized: %v, ", c.ComposerStartsMinimized)
output += fmt.Sprintf("SequencerStartsMinimized: %v, ", c.SequencerStartsMinimized)
output += fmt.Sprintf("GenericStartsMinimized: %v", c.GenericStartsMinimized)
output += fmt.Sprintf("HighlightColor: %s, ", c.HighlightColor)
output += fmt.Sprintf("BorderColor: %s", c.BorderColor)
output += "}"
return output
}

// LoadTUIConfigOrPanic loads the TUIConfigs from the given path. If the file
// cannot be loaded or parsed, the function will panic.
func LoadTUIConfigOrPanic(path string) TUIConfig {
viper.SetConfigFile(path)

if err := viper.ReadInConfig(); err != nil {
log.Fatalf("Error reading config file, %s", err)
panic(err)
}

var config TUIConfig
if err := viper.Unmarshal(&config); err != nil {
log.Fatalf("Unable to decode into struct, %v", err)
panic(err)
}

return config
}

// CreateTUIConfig creates a TUI configuration file and populates it
// with the defaults for the devrunner TUI. It will panic if the file
// cannot be created or written to.
func CreateTUIConfig(configPath string) {
_, err := os.Stat(configPath)
if err == nil {
log.Infof("%s already exists. Skipping initialization.\n", configPath)
return
}
// create an instance of the Config struct with some data
config := DefaultTUIConfig()

// open a file for writing
file, err := os.Create(configPath)
if err != nil {
panic(err)
}
defer file.Close()

// encode the struct to TOML and write to the file
if err := toml.NewEncoder(file).Encode(config); err != nil {
panic(err)
}
log.Infof("New TUI config file created successfully: %s\n", configPath)
}
3 changes: 3 additions & 0 deletions modules/cli/cmd/devrunner/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func runInitialization(c *cobra.Command, _ []string) {
config.CreateNetworksConfig(localBinPath, networksConfigPath, localNetworkName, localDenom)
networkConfigs := config.LoadNetworkConfigsOrPanic(networksConfigPath)

tuiConfigBath := filepath.Join(defaultDir, config.DefaultTUIConfigName)
config.CreateTUIConfig(tuiConfigBath)

configDirPath := filepath.Join(instanceDir, config.DefaultConfigDirName)
cmd.CreateDirOrPanic(configDirPath)

Expand Down
107 changes: 71 additions & 36 deletions modules/cli/cmd/devrunner/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,33 @@ func runCmdHandler(c *cobra.Command, _ []string) {

astriaDir := filepath.Join(homeDir, ".astria")

tuiConfigPath := filepath.Join(astriaDir, config.DefaultTUIConfigName)
tuiConfig := config.LoadTUIConfigOrPanic(tuiConfigPath)

instance := flagHandler.GetValue("instance")
config.IsInstanceNameValidOrPanic(instance)

// set the instance name from the correct source so that logging is handled
// correctly
if !flagHandler.GetChanged("instance") {
instance = tuiConfig.OverrideInstanceName
}

exportLogs := flagHandler.GetValue("export-logs") == "true"
logsDir := filepath.Join(astriaDir, instance, config.LogsDirName)
currentTime := time.Now()
appStartTime := currentTime.Format("20060102-150405") // YYYYMMDD-HHMMSS

cmd.CreateUILog(filepath.Join(astriaDir, instance))

// log the instance name in the tui logs once they are created
if !flagHandler.GetChanged("instance") {
log.Debug("Using overridden default instance name: ", instance)
} else {
log.Debug("Instance name: ", instance)
}
log.Debug(tuiConfig)

network := flagHandler.GetValue("network")

baseConfigPath := filepath.Join(astriaDir, instance, config.DefaultConfigDirName, config.DefaultBaseConfigName)
Expand Down Expand Up @@ -108,39 +125,48 @@ func runCmdHandler(c *cobra.Command, _ []string) {
seqReadinessCheck := processrunner.NewReadyChecker(seqRCOpts)
log.Debugf("arguments for sequencer service: %v", service.Args)
seqOpts := processrunner.NewProcessRunnerOpts{
Title: "Sequencer",
BinPath: sequencerPath,
Env: environment,
Args: service.Args,
ReadyCheck: &seqReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-sequencer.log"),
ExportLogs: exportLogs,
Title: "Sequencer",
BinPath: sequencerPath,
Env: environment,
Args: service.Args,
ReadyCheck: &seqReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-sequencer.log"),
ExportLogs: exportLogs,
StartMinimized: tuiConfig.SequencerStartsMinimized,
HighlightColor: tuiConfig.HighlightColor,
BorderColor: tuiConfig.BorderColor,
}
seqRunner = processrunner.NewProcessRunner(ctx, seqOpts)
case "composer":
composerPath := getFlagPath(c, "composer-path", "composer", service.LocalPath)
log.Debugf("arguments for composer service: %v", service.Args)
composerOpts := processrunner.NewProcessRunnerOpts{
Title: "Composer",
BinPath: composerPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-composer.log"),
ExportLogs: exportLogs,
Title: "Composer",
BinPath: composerPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-composer.log"),
ExportLogs: exportLogs,
StartMinimized: tuiConfig.ComposerStartsMinimized,
HighlightColor: tuiConfig.HighlightColor,
BorderColor: tuiConfig.BorderColor,
}
compRunner = processrunner.NewProcessRunner(ctx, composerOpts)
case "conductor":
conductorPath := getFlagPath(c, "conductor-path", "conductor", service.LocalPath)
log.Debugf("arguments for conductor service: %v", service.Args)
conductorOpts := processrunner.NewProcessRunnerOpts{
Title: "Conductor",
BinPath: conductorPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-conductor.log"),
ExportLogs: exportLogs,
Title: "Conductor",
BinPath: conductorPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-astria-conductor.log"),
ExportLogs: exportLogs,
StartMinimized: tuiConfig.ConductorStartsMinimized,
HighlightColor: tuiConfig.HighlightColor,
BorderColor: tuiConfig.BorderColor,
}
condRunner = processrunner.NewProcessRunner(ctx, conductorOpts)
case "cometbft":
Expand All @@ -158,25 +184,31 @@ func runCmdHandler(c *cobra.Command, _ []string) {
args := append([]string{"node", "--home", cometDataPath, "--log_level", serviceLogLevel}, service.Args...)
log.Debugf("arguments for cometbft service: %v", args)
cometOpts := processrunner.NewProcessRunnerOpts{
Title: "Comet BFT",
BinPath: cometbftPath,
Env: environment,
Args: args,
ReadyCheck: &cometReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-cometbft.log"),
ExportLogs: exportLogs,
Title: "Comet BFT",
BinPath: cometbftPath,
Env: environment,
Args: args,
ReadyCheck: &cometReadinessCheck,
LogPath: filepath.Join(logsDir, appStartTime+"-cometbft.log"),
ExportLogs: exportLogs,
StartMinimized: tuiConfig.CometBFTStartsMinimized,
HighlightColor: tuiConfig.HighlightColor,
BorderColor: tuiConfig.BorderColor,
}
cometRunner = processrunner.NewProcessRunner(ctx, cometOpts)
default:
log.Debugf("arguments for %s service: %v", label, service.Args)
genericOpts := processrunner.NewProcessRunnerOpts{
Title: service.Name,
BinPath: service.LocalPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-"+service.Name+".log"),
ExportLogs: exportLogs,
Title: service.Name,
BinPath: service.LocalPath,
Env: environment,
Args: service.Args,
ReadyCheck: nil,
LogPath: filepath.Join(logsDir, appStartTime+"-"+service.Name+".log"),
ExportLogs: exportLogs,
StartMinimized: tuiConfig.GenericStartsMinimized,
HighlightColor: tuiConfig.HighlightColor,
BorderColor: tuiConfig.BorderColor,
}
genericRunner := processrunner.NewProcessRunner(ctx, genericOpts)
genericRunners = append(genericRunners, genericRunner)
Expand All @@ -193,7 +225,10 @@ func runCmdHandler(c *cobra.Command, _ []string) {

// create and start ui app
app := ui.NewApp(runners)
app.Start()
// start the app with initial setting from the tui config, the border will
// always start on
appStartState := ui.NewStateStore(tuiConfig.AutoScroll, tuiConfig.WrapLines, tuiConfig.Borderless)
app.Start(appStartState)
}

// getFlagPath gets the override path from the flag. It returns the default
Expand Down
Loading

0 comments on commit 0932ce5

Please sign in to comment.