Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
278 lines
7.7 KiB
Go
278 lines
7.7 KiB
Go
package cmdmodel
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"github.com/ignite/cli/v29/ignite/internal/announcements"
|
|
"github.com/ignite/cli/v29/ignite/pkg/cliui/colors"
|
|
"github.com/ignite/cli/v29/ignite/pkg/cliui/icons"
|
|
cliuimodel "github.com/ignite/cli/v29/ignite/pkg/cliui/model"
|
|
"github.com/ignite/cli/v29/ignite/pkg/events"
|
|
)
|
|
|
|
const (
|
|
maxStatusEvents = 7
|
|
)
|
|
|
|
const (
|
|
stateChainServeStarting uint = iota
|
|
stateChainServeRunning
|
|
stateChainServeRebuilding
|
|
stateChainServeQuitting
|
|
)
|
|
|
|
var (
|
|
msgStopServe = colors.Faint("Press the 'q' key to stop serve")
|
|
msgWaitingFix = colors.Info("Waiting for a fix before retrying...")
|
|
)
|
|
|
|
type Context interface {
|
|
// Context returns the current context.
|
|
Context() context.Context
|
|
|
|
// SetContext updates the context with a new one.
|
|
SetContext(context.Context)
|
|
}
|
|
|
|
// NewChainServe returns a new UI model for the chain serve command.
|
|
func NewChainServe(mCtx Context, bus events.Provider, cmd tea.Cmd) ChainServe {
|
|
// Initialize a context and cancel function to stop execution
|
|
ctx, quit := context.WithCancel(mCtx.Context())
|
|
|
|
// Update the context to allow stopping by using the 'q' key
|
|
mCtx.SetContext(ctx)
|
|
|
|
return ChainServe{
|
|
cmd: cmd,
|
|
quit: quit,
|
|
startModel: cliuimodel.NewStatusEvents(bus, maxStatusEvents),
|
|
runModel: cliuimodel.NewEvents(bus),
|
|
rebuildModel: cliuimodel.NewStatusEvents(bus, maxStatusEvents),
|
|
quitModel: cliuimodel.NewEvents(bus),
|
|
}
|
|
}
|
|
|
|
// ChainServe defines a UI model for the chain serve command.
|
|
type ChainServe struct {
|
|
cmd tea.Cmd
|
|
quit context.CancelFunc
|
|
|
|
state uint // Keeps track of the model/view being displayed
|
|
broken bool // True when blockchain app's source code has issues
|
|
error error // Critical error returned during command execution
|
|
|
|
// Model definitions for the chain serve views
|
|
startModel cliuimodel.StatusEvents
|
|
runModel cliuimodel.Events
|
|
rebuildModel cliuimodel.StatusEvents
|
|
quitModel cliuimodel.Events
|
|
}
|
|
|
|
// Init is the first function that will be called.
|
|
// It returns a batch command that listen events and also runs the blockchain app.
|
|
func (m ChainServe) Init() tea.Cmd {
|
|
// On initialization wait for status events and start serving the blockchain
|
|
return tea.Batch(m.startModel.WaitEvent, m.cmd)
|
|
}
|
|
|
|
// Update is called when a message is received.
|
|
// It handles messages and executes the logic that updates the model.
|
|
func (m ChainServe) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
if checkQuitKeyMsg(msg) {
|
|
m.state = stateChainServeQuitting
|
|
}
|
|
|
|
switch msg := msg.(type) {
|
|
case cliuimodel.QuitMsg:
|
|
return m.processQuitMsg(msg)
|
|
case cliuimodel.ErrorMsg:
|
|
return m.processErrorMsg(msg)
|
|
case tea.KeyMsg:
|
|
return m.processKeyMsg(msg)
|
|
case cliuimodel.EventMsg:
|
|
return m.processEventMsg(msg)
|
|
default:
|
|
return m.updateCurrentModel(msg)
|
|
}
|
|
}
|
|
|
|
// View renders the UI after every update.
|
|
func (m ChainServe) View() string {
|
|
if m.error != nil {
|
|
return fmt.Sprintf("%s %s\n", icons.NotOK, colors.Error(m.error.Error()))
|
|
}
|
|
|
|
var view strings.Builder
|
|
|
|
switch m.state {
|
|
case stateChainServeStarting:
|
|
view.WriteString(m.renderStartView())
|
|
case stateChainServeRunning:
|
|
view.WriteString(m.renderRunView())
|
|
case stateChainServeRebuilding:
|
|
view.WriteString(m.renderRebuildView())
|
|
case stateChainServeQuitting:
|
|
view.WriteString(m.renderQuitView())
|
|
}
|
|
|
|
if m.state != stateChainServeQuitting {
|
|
view.WriteString(m.renderActions())
|
|
}
|
|
|
|
return cliuimodel.FormatView(view.String())
|
|
}
|
|
|
|
func (m ChainServe) updateCurrentModel(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
var cmd tea.Cmd
|
|
|
|
switch m.state {
|
|
case stateChainServeStarting:
|
|
m.startModel, cmd = m.startModel.Update(msg)
|
|
case stateChainServeRunning:
|
|
m.runModel, cmd = m.runModel.Update(msg)
|
|
case stateChainServeRebuilding:
|
|
m.rebuildModel, cmd = m.rebuildModel.Update(msg)
|
|
case stateChainServeQuitting:
|
|
m.quitModel, cmd = m.quitModel.Update(msg)
|
|
}
|
|
|
|
return m, cmd
|
|
}
|
|
|
|
func (m ChainServe) processQuitMsg(cliuimodel.QuitMsg) (tea.Model, tea.Cmd) {
|
|
return m, tea.Quit
|
|
}
|
|
|
|
func (m ChainServe) processErrorMsg(msg cliuimodel.ErrorMsg) (tea.Model, tea.Cmd) {
|
|
m.error = msg.Error
|
|
return m, tea.Quit
|
|
}
|
|
|
|
func (m ChainServe) processKeyMsg(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
|
if checkQuitKeyMsg(msg) {
|
|
// Cancel the context to signal stop
|
|
m.quit()
|
|
}
|
|
|
|
return m, nil
|
|
}
|
|
|
|
func (m ChainServe) processEventMsg(msg cliuimodel.EventMsg) (tea.Model, tea.Cmd) {
|
|
// When an error event is received it means there is an issue with
|
|
// the blockchain app's source code that the user must fix.
|
|
m.broken = msg.Group == events.GroupError
|
|
|
|
// UI responds to key press or mouse events by default but we use
|
|
// events and the events bus to interact with the UI during execution.
|
|
// Check if the state must be changed to switch to a different view.
|
|
switch m.state {
|
|
case stateChainServeStarting:
|
|
// Start view displays status events until the blockchain is running or an
|
|
// error event is received in which case it displays the run view with an
|
|
// error traceback and waits until the issue is fixed.
|
|
// When the status finish event is not an error it means that the blockchain
|
|
// started successfully and the run view is displayed.
|
|
if msg.ProgressIndication == events.IndicationFinish {
|
|
m.state = stateChainServeRunning
|
|
}
|
|
case stateChainServeRunning:
|
|
// Run view shows account addresses, API URLs and the paths required to
|
|
// have a context on the running blockchain app and waits for errors or
|
|
// changes in the blockchain app source code.
|
|
// If an error event is received during run it means that there is an error
|
|
// in the app source code in which case the error message and traceback are
|
|
// displayed until the code is fixed, or otherwise when an status event is
|
|
// received it means that the code changed so the app must be rebuilt.
|
|
if m.broken {
|
|
// Clear events to only display the error received with the last event message
|
|
m.runModel.ClearEvents()
|
|
} else if msg.InProgress() {
|
|
// When a status event is received during run it means something
|
|
// changed in the source code which triggers the blockchain rebuild.
|
|
m.runModel.ClearEvents()
|
|
m.state = stateChainServeRebuilding
|
|
}
|
|
case stateChainServeRebuilding:
|
|
// Rebuild view is similar to run view but only displayed when the source
|
|
// code changes and the blockchain is rebuilt.
|
|
// When the status finish event is not an error it means that the blockchain
|
|
// was rebuilt successfully and the run view is displayed.
|
|
if msg.ProgressIndication == events.IndicationFinish {
|
|
m.rebuildModel.ClearEvents()
|
|
m.state = stateChainServeRunning
|
|
}
|
|
}
|
|
|
|
// Update the model that is being displayed
|
|
return m.updateCurrentModel(msg)
|
|
}
|
|
|
|
func (m ChainServe) renderActions() string {
|
|
return fmt.Sprintf("\n%s\n", msgStopServe)
|
|
}
|
|
|
|
func (m ChainServe) renderStartView() string {
|
|
return m.startModel.View()
|
|
}
|
|
|
|
func (m ChainServe) renderRunView() string {
|
|
var view strings.Builder
|
|
|
|
if !m.broken {
|
|
view.WriteString("Blockchain is running\n\n")
|
|
}
|
|
|
|
view.WriteString(m.runModel.View())
|
|
|
|
if m.broken {
|
|
fmt.Fprintf(&view, "\n%s\n", msgWaitingFix)
|
|
}
|
|
|
|
return view.String()
|
|
}
|
|
|
|
func (m ChainServe) renderRebuildView() string {
|
|
var view strings.Builder
|
|
|
|
if !m.broken {
|
|
view.WriteString("Changes detected, restarting...\n\n")
|
|
}
|
|
|
|
view.WriteString(m.rebuildModel.View())
|
|
|
|
if m.broken {
|
|
fmt.Fprintf(&view, "\n%s\n", msgWaitingFix)
|
|
}
|
|
|
|
return view.String()
|
|
}
|
|
|
|
func (m ChainServe) renderQuitView() string {
|
|
var view strings.Builder
|
|
|
|
// Display the events received during quit
|
|
if s := m.quitModel.View(); s != "" {
|
|
view.WriteString(s)
|
|
view.WriteRune('\n')
|
|
}
|
|
|
|
fmt.Fprintf(&view, "%s %s\n\n", icons.Info, colors.Info("Stopped"))
|
|
view.WriteString(announcements.Fetch())
|
|
|
|
return view.String()
|
|
}
|
|
|
|
func checkQuitKeyMsg(m tea.Msg) bool {
|
|
msg, ok := m.(tea.KeyMsg)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
key := msg.String()
|
|
|
|
return key == "q" || key == "ctrl+c"
|
|
}
|