From 88ba5d22d763dbd22cf1500186a408e1936e7e8e Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 24 Oct 2024 17:38:17 -0400 Subject: [PATCH 01/24] test: protocol snapshots and 100% --- ui/protocol_test.go | 104 +++++++++++++++++- .../Test_ProtocolSnapshot/Hidden.golden | 0 .../Test_ProtocolSnapshot/HiddenHeight.golden | 0 .../NoVoteOrUpgrade.golden | 7 ++ .../NoVoteOrUpgradeSmall.golden | 7 ++ .../Test_ProtocolSnapshot/Visible.golden | 7 ++ .../Test_ProtocolSnapshot/VisibleSmall.golden | 7 ++ 7 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 ui/testdata/Test_ProtocolSnapshot/Hidden.golden create mode 100644 ui/testdata/Test_ProtocolSnapshot/HiddenHeight.golden create mode 100644 ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgrade.golden create mode 100644 ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgradeSmall.golden create mode 100644 ui/testdata/Test_ProtocolSnapshot/Visible.golden create mode 100644 ui/testdata/Test_ProtocolSnapshot/VisibleSmall.golden diff --git a/ui/protocol_test.go b/ui/protocol_test.go index b757b7ef..0ac4dc27 100644 --- a/ui/protocol_test.go +++ b/ui/protocol_test.go @@ -4,12 +4,105 @@ import ( "bytes" "github.com/algorandfoundation/hack-tui/internal" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/exp/golden" "github.com/charmbracelet/x/exp/teatest" "testing" "time" ) -func Test_ProtocolViewRender(t *testing.T) { +var protocolViewSnapshots = map[string]ProtocolViewModel{ + "Hidden": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: true, + NeedsUpdate: true, + LastRound: 0, + }, + TerminalWidth: 60, + TerminalHeight: 40, + IsVisible: false, + }, + "HiddenHeight": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: true, + NeedsUpdate: true, + LastRound: 0, + }, + TerminalWidth: 70, + TerminalHeight: 20, + IsVisible: true, + }, + "Visible": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: true, + NeedsUpdate: true, + LastRound: 0, + }, + TerminalWidth: 160, + TerminalHeight: 80, + IsVisible: true, + }, + "VisibleSmall": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: true, + NeedsUpdate: true, + LastRound: 0, + }, + TerminalWidth: 80, + TerminalHeight: 40, + IsVisible: true, + }, + "NoVoteOrUpgrade": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: false, + NeedsUpdate: false, + LastRound: 0, + }, + TerminalWidth: 160, + TerminalHeight: 80, + IsVisible: true, + }, + "NoVoteOrUpgradeSmall": { + Data: internal.StatusModel{ + State: "SYNCING", + Version: "v0.0.0-test", + Network: "test-v1", + Voting: false, + NeedsUpdate: false, + LastRound: 0, + }, + TerminalWidth: 80, + TerminalHeight: 40, + IsVisible: true, + }, +} + +func Test_ProtocolSnapshot(t *testing.T) { + for name, model := range protocolViewSnapshots { + t.Run(name, func(t *testing.T) { + got := ansi.Strip(model.View()) + golden.RequireEqual(t, []byte(got)) + }) + } +} + +// Test_ProtocolMessages handles any additional tests like sending messages +func Test_ProtocolMessages(t *testing.T) { state := internal.StateModel{ Status: internal.StatusModel{ LastRound: 1337, @@ -41,7 +134,14 @@ func Test_ProtocolViewRender(t *testing.T) { teatest.WithCheckInterval(time.Millisecond*100), teatest.WithDuration(time.Second*3), ) - + tm.Send(internal.StatusModel{ + State: "", + Version: "", + Network: "", + Voting: false, + NeedsUpdate: false, + LastRound: 0, + }) // Send hide key tm.Send(tea.KeyMsg{ Type: tea.KeyRunes, diff --git a/ui/testdata/Test_ProtocolSnapshot/Hidden.golden b/ui/testdata/Test_ProtocolSnapshot/Hidden.golden new file mode 100644 index 00000000..e69de29b diff --git a/ui/testdata/Test_ProtocolSnapshot/HiddenHeight.golden b/ui/testdata/Test_ProtocolSnapshot/HiddenHeight.golden new file mode 100644 index 00000000..e69de29b diff --git a/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgrade.golden b/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgrade.golden new file mode 100644 index 00000000..6eca90f5 --- /dev/null +++ b/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgrade.golden @@ -0,0 +1,7 @@ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ Node: v0.0.0-test │ +│ │ +│ Network: test-v1 │ +│ │ +│ Protocol Voting: false │ +╰──────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgradeSmall.golden b/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgradeSmall.golden new file mode 100644 index 00000000..a0cc43b9 --- /dev/null +++ b/ui/testdata/Test_ProtocolSnapshot/NoVoteOrUpgradeSmall.golden @@ -0,0 +1,7 @@ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ Node: v0.0.0-test │ +│ Network: test-v1 │ +│ Protocol Voting: false │ +│ │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/ui/testdata/Test_ProtocolSnapshot/Visible.golden b/ui/testdata/Test_ProtocolSnapshot/Visible.golden new file mode 100644 index 00000000..a8adee3f --- /dev/null +++ b/ui/testdata/Test_ProtocolSnapshot/Visible.golden @@ -0,0 +1,7 @@ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ Node: v0.0.0-test [UPDATE AVAILABLE] │ +│ │ +│ Network: test-v1 │ +│ │ +│ Protocol Voting: true │ +╰──────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file diff --git a/ui/testdata/Test_ProtocolSnapshot/VisibleSmall.golden b/ui/testdata/Test_ProtocolSnapshot/VisibleSmall.golden new file mode 100644 index 00000000..10a5f701 --- /dev/null +++ b/ui/testdata/Test_ProtocolSnapshot/VisibleSmall.golden @@ -0,0 +1,7 @@ +╭──────────────────────────────────────────────────────────────────────────────╮ +│ Node: v0.0.0-test │ +│ Network: test-v1 │ +│ Protocol Voting: true │ +│ Upgrade Available: true │ +│ │ +╰──────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file From 967fa6ba70b4335e712b17b5459d89c58c560884 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 24 Oct 2024 18:02:49 -0400 Subject: [PATCH 02/24] test: status snapshots and 100% --- ui/status_test.go | 67 ++++++++++++++++++- ui/testdata/Test_StatusSnapshot/Hidden.golden | 0 .../Test_StatusSnapshot/Loading.golden | 6 ++ .../Test_StatusSnapshot/Syncing.golden | 7 ++ 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 ui/testdata/Test_StatusSnapshot/Hidden.golden create mode 100644 ui/testdata/Test_StatusSnapshot/Loading.golden create mode 100644 ui/testdata/Test_StatusSnapshot/Syncing.golden diff --git a/ui/status_test.go b/ui/status_test.go index 92571c8f..5d908dc0 100644 --- a/ui/status_test.go +++ b/ui/status_test.go @@ -4,12 +4,74 @@ import ( "bytes" "github.com/algorandfoundation/hack-tui/internal" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/exp/golden" "github.com/charmbracelet/x/exp/teatest" "testing" "time" ) -func Test_StatusViewRender(t *testing.T) { +var statusViewSnapshots = map[string]StatusViewModel{ + "Syncing": { + Data: &internal.StateModel{ + Status: internal.StatusModel{ + LastRound: 1337, + NeedsUpdate: true, + State: "SYNCING", + }, + Metrics: internal.MetricsModel{ + RoundTime: 0, + TX: 0, + }, + }, + TerminalWidth: 180, + TerminalHeight: 80, + IsVisible: true, + }, + "Hidden": { + Data: &internal.StateModel{ + Status: internal.StatusModel{ + LastRound: 1337, + NeedsUpdate: true, + State: "SYNCING", + }, + Metrics: internal.MetricsModel{ + RoundTime: 0, + TX: 0, + }, + }, + TerminalWidth: 180, + TerminalHeight: 80, + IsVisible: false, + }, + "Loading": { + Data: &internal.StateModel{ + Status: internal.StatusModel{ + LastRound: 1337, + NeedsUpdate: true, + State: "SYNCING", + }, + Metrics: internal.MetricsModel{ + RoundTime: 0, + TX: 0, + }, + }, + TerminalWidth: 0, + TerminalHeight: 0, + IsVisible: true, + }, +} + +func Test_StatusSnapshot(t *testing.T) { + for name, model := range statusViewSnapshots { + t.Run(name, func(t *testing.T) { + got := ansi.Strip(model.View()) + golden.RequireEqual(t, []byte(got)) + }) + } +} + +func Test_StatusMessages(t *testing.T) { state := internal.StateModel{ Status: internal.StatusModel{ LastRound: 1337, @@ -45,6 +107,9 @@ func Test_StatusViewRender(t *testing.T) { teatest.WithDuration(time.Second*3), ) + // Send the state + tm.Send(state) + // Send hide key tm.Send(tea.KeyMsg{ Type: tea.KeyRunes, diff --git a/ui/testdata/Test_StatusSnapshot/Hidden.golden b/ui/testdata/Test_StatusSnapshot/Hidden.golden new file mode 100644 index 00000000..e69de29b diff --git a/ui/testdata/Test_StatusSnapshot/Loading.golden b/ui/testdata/Test_StatusSnapshot/Loading.golden new file mode 100644 index 00000000..fe45233a --- /dev/null +++ b/ui/testdata/Test_StatusSnapshot/Loading.golden @@ -0,0 +1,6 @@ +Loading... + + + + + diff --git a/ui/testdata/Test_StatusSnapshot/Syncing.golden b/ui/testdata/Test_StatusSnapshot/Syncing.golden new file mode 100644 index 00000000..7edf0f7e --- /dev/null +++ b/ui/testdata/Test_StatusSnapshot/Syncing.golden @@ -0,0 +1,7 @@ +╭────────────────────────────────────────────────────────────────────────────────────────╮ +│ Latest Round: 1337 SYNCING │ +│ │ +│ -- 0 round average -- │ +│ Round time: 0.00s 0 KB/s TX │ +│ TPS: 0.00 0 KB/s RX │ +╰────────────────────────────────────────────────────────────────────────────────────────╯ \ No newline at end of file From 778ad682cbcb04f35ca5696b75ab96f7f00e87b3 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 24 Oct 2024 18:58:00 -0400 Subject: [PATCH 03/24] test: error page snapshots and 100% --- ui/error_test.go | 52 +++++++++++++++++++ ui/testdata/Test_ErrorSnapshot/Visible.golden | 24 +++++++++ 2 files changed, 76 insertions(+) create mode 100644 ui/error_test.go create mode 100644 ui/testdata/Test_ErrorSnapshot/Visible.golden diff --git a/ui/error_test.go b/ui/error_test.go new file mode 100644 index 00000000..10020eac --- /dev/null +++ b/ui/error_test.go @@ -0,0 +1,52 @@ +package ui + +import ( + "bytes" + "github.com/algorandfoundation/hack-tui/ui/controls" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/exp/golden" + "github.com/charmbracelet/x/exp/teatest" + "testing" + "time" +) + +func Test_ErrorSnapshot(t *testing.T) { + t.Run("Visible", func(t *testing.T) { + model := ErrorViewModel{ + Height: 20, + Width: 40, + controls: controls.New(" Error "), + Message: "a test error", + } + got := ansi.Strip(model.View()) + golden.RequireEqual(t, []byte(got)) + }) +} + +func Test_ErrorMessages(t *testing.T) { + tm := teatest.NewTestModel( + t, ErrorViewModel{Message: "a test error"}, + teatest.WithInitialTermSize(120, 80), + ) + + // Wait for prompt to exit + teatest.WaitFor( + t, tm.Output(), + func(bts []byte) bool { + return bytes.Contains(bts, []byte("a test error")) + }, + teatest.WithCheckInterval(time.Millisecond*100), + teatest.WithDuration(time.Second*3), + ) + // Resize Message + tm.Send(tea.WindowSizeMsg{ + Width: 50, + Height: 20, + }) + + // Send quit key + tm.Send(tea.QuitMsg{}) + + tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second)) +} diff --git a/ui/testdata/Test_ErrorSnapshot/Visible.golden b/ui/testdata/Test_ErrorSnapshot/Visible.golden new file mode 100644 index 00000000..80bcfb88 --- /dev/null +++ b/ui/testdata/Test_ErrorSnapshot/Visible.golden @@ -0,0 +1,24 @@ + + + + + + + + + + + a test error + + + + + + + + + + + ╭───────╮ +───────────────────────────────────┤ Error ├─────────────────────────────────── + ╰───────╯ \ No newline at end of file From b6c76112a92c5f956917e1bfbd63674184665afd Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Thu, 24 Oct 2024 19:17:12 -0400 Subject: [PATCH 04/24] test: controls snapshots --- ui/controls/controls_test.go | 12 +++++++++++- ui/controls/testdata/Test_Snapshot/Visible.golden | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 ui/controls/testdata/Test_Snapshot/Visible.golden diff --git a/ui/controls/controls_test.go b/ui/controls/controls_test.go index ed1811a1..2693afa3 100644 --- a/ui/controls/controls_test.go +++ b/ui/controls/controls_test.go @@ -3,12 +3,22 @@ package controls import ( "bytes" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/exp/golden" "github.com/charmbracelet/x/exp/teatest" "testing" "time" ) -func Test_Controls(t *testing.T) { +func Test_Snapshot(t *testing.T) { + t.Run("Visible", func(t *testing.T) { + model := New(" test ") + got := ansi.Strip(model.View()) + golden.RequireEqual(t, []byte(got)) + }) +} + +func Test_Messages(t *testing.T) { expected := "(q)uit | (d)elete | (g)enerate | (t)xn | (h)ide" // Create the Model m := New(expected) diff --git a/ui/controls/testdata/Test_Snapshot/Visible.golden b/ui/controls/testdata/Test_Snapshot/Visible.golden new file mode 100644 index 00000000..8f20fb4b --- /dev/null +++ b/ui/controls/testdata/Test_Snapshot/Visible.golden @@ -0,0 +1,3 @@ + ╭──────╮ +────────────────────────────────────┤ test ├──────────────────────────────────── + ╰──────╯ \ No newline at end of file From f328f0c92c08d2663e3880af779030246add5c6e Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Fri, 25 Oct 2024 07:39:26 -0400 Subject: [PATCH 05/24] test: accounts snapshots --- ui/pages/accounts/accounts_test.go | 106 ++++++++++++++++++ .../testdata/Test_Snapshot/Visible.golden | 26 +++++ 2 files changed, 132 insertions(+) create mode 100644 ui/pages/accounts/accounts_test.go create mode 100644 ui/pages/accounts/testdata/Test_Snapshot/Visible.golden diff --git a/ui/pages/accounts/accounts_test.go b/ui/pages/accounts/accounts_test.go new file mode 100644 index 00000000..d6afa218 --- /dev/null +++ b/ui/pages/accounts/accounts_test.go @@ -0,0 +1,106 @@ +package accounts + +import ( + "bytes" + "github.com/algorandfoundation/hack-tui/api" + "github.com/algorandfoundation/hack-tui/internal" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/x/ansi" + "github.com/charmbracelet/x/exp/golden" + "github.com/charmbracelet/x/exp/teatest" + "testing" + "time" +) + +func Test_Snapshot(t *testing.T) { + t.Run("Visible", func(t *testing.T) { + model := New(&internal.StateModel{ + Status: internal.StatusModel{}, + Metrics: internal.MetricsModel{}, + Accounts: nil, + ParticipationKeys: nil, + Admin: false, + Watching: false, + }) + got := ansi.Strip(model.View()) + golden.RequireEqual(t, []byte(got)) + }) +} + +func Test_Messages(t *testing.T) { + var testKeys = []api.ParticipationKey{ + { + Address: "ABC", + EffectiveFirstValid: nil, + EffectiveLastValid: nil, + Id: "", + Key: api.AccountParticipation{ + SelectionParticipationKey: nil, + StateProofKey: nil, + VoteFirstValid: 0, + VoteKeyDilution: 0, + VoteLastValid: 0, + VoteParticipationKey: nil, + }, + LastBlockProposal: nil, + LastStateProof: nil, + LastVote: nil, + }, + } + sm := &internal.StateModel{ + Status: internal.StatusModel{}, + Metrics: internal.MetricsModel{}, + Accounts: nil, + ParticipationKeys: &testKeys, + Admin: false, + Watching: false, + } + values := make(map[string]internal.Account) + for _, key := range *sm.ParticipationKeys { + val, ok := values[key.Address] + if !ok { + values[key.Address] = internal.Account{ + Address: key.Address, + Status: "Offline", + Balance: 0, + Expires: time.Unix(0, 0), + Keys: 1, + } + } else { + val.Keys++ + values[key.Address] = val + } + } + sm.Accounts = values + // Create the Model + m := New(sm) + + tm := teatest.NewTestModel( + t, m, + teatest.WithInitialTermSize(80, 40), + ) + + // Wait for prompt to exit + teatest.WaitFor( + t, tm.Output(), + func(bts []byte) bool { + return bytes.Contains(bts, []byte("(k)eys")) + }, + teatest.WithCheckInterval(time.Millisecond*100), + teatest.WithDuration(time.Second*3), + ) + + tm.Send(*sm) + + tm.Send(tea.KeyMsg{ + Type: tea.KeyRunes, + Runes: []rune("enter"), + }) + + tm.Send(tea.KeyMsg{ + Type: tea.KeyRunes, + Runes: []rune("ctrl+c"), + }) + + tm.WaitFinished(t, teatest.WithFinalTimeout(time.Second)) +} diff --git a/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden b/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden new file mode 100644 index 00000000..0c6418cd --- /dev/null +++ b/ui/pages/accounts/testdata/Test_Snapshot/Visible.golden @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + ╭──────────────────────────────────────────╮ +──────────────────┤ (g)enerate | (a)ccounts | (k)eys | (t)xn ├────────────────── + ╰──────────────────────────────────────────╯ \ No newline at end of file From 2863927cbeff2628df2be05ec728e73033b2860b Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Fri, 25 Oct 2024 20:24:41 -0400 Subject: [PATCH 06/24] build: go mod tidy --- go.mod | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 59ef2490..1cbf71b9 100644 --- a/go.mod +++ b/go.mod @@ -27,8 +27,8 @@ require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymanbagabas/go-udiff v0.2.0 // indirect - github.com/charmbracelet/x/ansi v0.3.2 // indirect - github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b // indirect + github.com/charmbracelet/x/ansi v0.3.2 + github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b github.com/charmbracelet/x/term v0.2.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect From 1d37a37192e68e169e991d15383e6562ae63aa02 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 13:43:56 -0500 Subject: [PATCH 07/24] fix: RX/TX display --- internal/metrics.go | 3 +++ internal/state.go | 11 +++++++++-- ui/status.go | 20 ++++++++++++++++++-- 3 files changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/metrics.go b/internal/metrics.go index d2403094..b10a9f40 100644 --- a/internal/metrics.go +++ b/internal/metrics.go @@ -17,6 +17,9 @@ type MetricsModel struct { TPS float64 RX int TX int + LastTS time.Time + LastRX int + LastTX int } type MetricsResponse map[string]int diff --git a/internal/state.go b/internal/state.go index c20508c0..e14c07aa 100644 --- a/internal/state.go +++ b/internal/state.go @@ -91,8 +91,15 @@ func (s *StateModel) UpdateMetricsFromRPC(ctx context.Context, client *api.Clien } if err == nil { s.Metrics.Enabled = true - s.Metrics.TX = res["algod_network_sent_bytes_total"] - s.Metrics.RX = res["algod_network_received_bytes_total"] + now := time.Now() + diff := now.Sub(s.Metrics.LastTS) + + s.Metrics.TX = max(0, int(float64(res["algod_network_sent_bytes_total"]-s.Metrics.LastTX)/diff.Seconds())) + s.Metrics.RX = max(0, int(float64(res["algod_network_received_bytes_total"]-s.Metrics.LastRX)/diff.Seconds())) + + s.Metrics.LastTS = now + s.Metrics.LastTX = res["algod_network_sent_bytes_total"] + s.Metrics.LastRX = res["algod_network_received_bytes_total"] } } func (s *StateModel) UpdateAccounts(client *api.ClientWithResponses) { diff --git a/ui/status.go b/ui/status.go index 281288bf..fd9cce2a 100644 --- a/ui/status.go +++ b/ui/status.go @@ -6,6 +6,7 @@ import ( "github.com/algorandfoundation/hack-tui/ui/style" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "math" "strconv" "strings" "time" @@ -44,6 +45,21 @@ func (m StatusViewModel) HandleMessage(msg tea.Msg) (StatusViewModel, tea.Cmd) { return m, nil } +func getBitRate(bytes int) string { + txString := fmt.Sprintf("%d B/s ", bytes) + if bytes >= 1024 { + txString = fmt.Sprintf("%d KB/s ", bytes/(1<<10)) + } + if bytes >= int(math.Pow(1024, 2)) { + txString = fmt.Sprintf("%d MB/s ", bytes/(1<<20)) + } + if bytes >= int(math.Pow(1024, 3)) { + txString = fmt.Sprintf("%d GB/s ", bytes/(1<<20)) + } + + return txString +} + // View handles the render cycle func (m StatusViewModel) View() string { if !m.IsVisible { @@ -70,13 +86,13 @@ func (m StatusViewModel) View() string { row1 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) beginning = style.Blue.Render(" Round time: ") + fmt.Sprintf("%.2fs", float64(m.Data.Metrics.RoundTime)/float64(time.Second)) - end = fmt.Sprintf("%d KB/s ", m.Data.Metrics.TX/1024) + style.Green.Render("TX ") + end = getBitRate(m.Data.Metrics.TX) + style.Green.Render("TX ") middle = strings.Repeat(" ", max(0, size-(lipgloss.Width(beginning)+lipgloss.Width(end)+2))) row2 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) beginning = style.Blue.Render(" TPS: ") + fmt.Sprintf("%.2f", m.Data.Metrics.TPS) - end = fmt.Sprintf("%d KB/s ", m.Data.Metrics.RX/1024) + style.Green.Render("RX ") + end = getBitRate(m.Data.Metrics.RX) + style.Green.Render("RX ") middle = strings.Repeat(" ", max(0, size-(lipgloss.Width(beginning)+lipgloss.Width(end)+2))) row3 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) From fe7325a67570dea86ae9ba099ce98e5bc27e0a17 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 14:42:26 -0500 Subject: [PATCH 08/24] fix: configuration override order --- cmd/root.go | 59 +++++++++++++++---- cmd/root_test.go | 18 +++++- cmd/status.go | 1 + .../{ => Test_InitConfig}/algod.admin.token | 0 .../{ => Test_InitConfig}/config.json | 0 .../algod.admin.token | 1 + .../Test_InitConfigWithoutEndpoint/algod.net | 1 + .../config.json | 2 + 8 files changed, 68 insertions(+), 14 deletions(-) rename cmd/testdata/{ => Test_InitConfig}/algod.admin.token (100%) rename cmd/testdata/{ => Test_InitConfig}/config.json (100%) create mode 100644 cmd/testdata/Test_InitConfigWithoutEndpoint/algod.admin.token create mode 100644 cmd/testdata/Test_InitConfigWithoutEndpoint/algod.net create mode 100644 cmd/testdata/Test_InitConfigWithoutEndpoint/config.json diff --git a/cmd/root.go b/cmd/root.go index 1f04e8e9..7dc14c62 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -39,6 +39,7 @@ var ( }, RunE: func(cmd *cobra.Command, args []string) error { log.SetOutput(cmd.OutOrStdout()) + initConfig() client, err := getClient() cobra.CheckErr(err) @@ -106,7 +107,7 @@ func check(err interface{}) { // Handle global flags and set usage templates func init() { log.SetReportTimestamp(false) - initConfig() + // Configure Version if Version == "" { Version = "unknown (built from source)" @@ -142,6 +143,15 @@ type AlgodConfig struct { EndpointAddress string `json:"EndpointAddress"` } +func replaceEndpointUrl(s string) string { + s = strings.Replace(s, "\n", "", 1) + s = strings.Replace(s, "0.0.0.0", "127.0.0.1", 1) + s = strings.Replace(s, "[::]", "127.0.0.1", 1) + return s +} +func hasWildcardEndpointUrl(s string) bool { + return strings.Contains(s, "0.0.0.0") || strings.Contains(s, "::") +} func initConfig() { // Find home directory. home, err := os.UserHomeDir() @@ -159,12 +169,17 @@ func initConfig() { // Load Configurations viper.AutomaticEnv() - err = viper.ReadInConfig() + _ = viper.ReadInConfig() + + // Check for server + loadedServer := viper.GetString("server") + loadedToken := viper.GetString("token") + // Load ALGORAND_DATA/config.json algorandData, exists := os.LookupEnv("ALGORAND_DATA") // Load the Algorand Data Configuration - if exists && algorandData != "" { + if exists && algorandData != "" && loadedServer == "" { // Placeholder for Struct var algodConfig AlgodConfig @@ -183,23 +198,41 @@ func initConfig() { err = configFile.Close() check(err) - // Replace catchall address with localhost - if strings.Contains(algodConfig.EndpointAddress, "0.0.0.0") { - algodConfig.EndpointAddress = strings.Replace(algodConfig.EndpointAddress, "0.0.0.0", "127.0.0.1", 1) + // Check for endpoint address + if hasWildcardEndpointUrl(algodConfig.EndpointAddress) { + algodConfig.EndpointAddress = replaceEndpointUrl(algodConfig.EndpointAddress) + } else { + // Assume it is not set, try to discover the port from the network file + networkPath := algorandData + "/algod.net" + networkFile, err := os.Open(networkPath) + check(err) + + byteValue, err = io.ReadAll(networkFile) + check(err) + + if hasWildcardEndpointUrl(string(byteValue)) { + algodConfig.EndpointAddress = replaceEndpointUrl(string(byteValue)) + } else { + algodConfig.EndpointAddress = string(byteValue) + } + } - // Handle Token Path - tokenPath := algorandData + "/algod.admin.token" + if loadedToken == "" { + // Handle Token Path + tokenPath := algorandData + "/algod.admin.token" - tokenFile, err := os.Open(tokenPath) - check(err) + tokenFile, err := os.Open(tokenPath) + check(err) - byteValue, err = io.ReadAll(tokenFile) - check(err) + byteValue, err = io.ReadAll(tokenFile) + check(err) + + viper.Set("token", strings.Replace(string(byteValue), "\n", "", 1)) + } // Set the server configuration viper.Set("server", "http://"+algodConfig.EndpointAddress) - viper.Set("token", string(byteValue)) viper.Set("data", dataConfigPath) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 0b7b8b7e..6c003a32 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -21,12 +21,28 @@ func Test_ExecuteRootCommand(t *testing.T) { func Test_InitConfig(t *testing.T) { cwd, _ := os.Getwd() - t.Setenv("ALGORAND_DATA", cwd+"/testdata") + t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfig") initConfig() server := viper.Get("server") if server == "" { t.Fatal("Invalid Server") } + if server != "http://127.0.0.1:8080" { + t.Fatal("Invalid Server") + } +} +func Test_InitConfigWithoutEndpoint(t *testing.T) { + cwd, _ := os.Getwd() + t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithoutEndpoint") + + initConfig() + server := viper.Get("server") + if server == "" { + t.Fatal("Invalid Server") + } + if server != "http://127.0.0.1:8080" { + t.Fatal("Invalid Server") + } } diff --git a/cmd/status.go b/cmd/status.go index 3bff01e3..5af7f38b 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -19,6 +19,7 @@ var statusCmd = &cobra.Command{ Short: "Get the node status", Long: style.Purple(BANNER) + "\n" + style.LightBlue("View the node status"), RunE: func(cmd *cobra.Command, args []string) error { + initConfig() if viper.GetString("server") == "" { return errors.New(style.Magenta("server is required")) } diff --git a/cmd/testdata/algod.admin.token b/cmd/testdata/Test_InitConfig/algod.admin.token similarity index 100% rename from cmd/testdata/algod.admin.token rename to cmd/testdata/Test_InitConfig/algod.admin.token diff --git a/cmd/testdata/config.json b/cmd/testdata/Test_InitConfig/config.json similarity index 100% rename from cmd/testdata/config.json rename to cmd/testdata/Test_InitConfig/config.json diff --git a/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.admin.token b/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.admin.token new file mode 100644 index 00000000..71b7a719 --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.admin.token @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.net b/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.net new file mode 100644 index 00000000..7985b074 --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithoutEndpoint/algod.net @@ -0,0 +1 @@ +[::]:8080 \ No newline at end of file diff --git a/cmd/testdata/Test_InitConfigWithoutEndpoint/config.json b/cmd/testdata/Test_InitConfigWithoutEndpoint/config.json new file mode 100644 index 00000000..7a73a41b --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithoutEndpoint/config.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file From 0938a78c915fccbac1940429578604e1a626c6af Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 15:01:01 -0500 Subject: [PATCH 09/24] feat: handle invalid configuration and token gracefully --- cmd/root.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 7dc14c62..8e4402ca 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -3,6 +3,7 @@ package cmd import ( "context" "encoding/json" + "fmt" "github.com/algorandfoundation/hack-tui/api" "github.com/algorandfoundation/hack-tui/internal" "github.com/algorandfoundation/hack-tui/ui" @@ -40,11 +41,23 @@ var ( RunE: func(cmd *cobra.Command, args []string) error { log.SetOutput(cmd.OutOrStdout()) initConfig() + + if viper.GetString("server") == "" { + return fmt.Errorf(style.Red.Render("server is required")) + } + if viper.GetString("token") == "" { + return fmt.Errorf(style.Red.Render("token is required")) + } + client, err := getClient() cobra.CheckErr(err) partkeys, err := internal.GetPartKeys(context.Background(), client) - cobra.CheckErr(err) + if err != nil { + return fmt.Errorf( + style.Red.Render("failed to get participation keys: %s"), + err) + } state := internal.StateModel{ Status: internal.StatusModel{ From 3c88bbb18c9683071b16ed46e9db3e14ea20ee29 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 15:26:51 -0500 Subject: [PATCH 10/24] test: fix test state --- cmd/root_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/root_test.go b/cmd/root_test.go index 6c003a32..cff94e1a 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -21,6 +21,8 @@ func Test_ExecuteRootCommand(t *testing.T) { func Test_InitConfig(t *testing.T) { cwd, _ := os.Getwd() + viper.Set("token", "") + viper.Set("server", "") t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfig") initConfig() From c16080f4e373d48efc259814d32118720f4a4318 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 15:23:05 -0500 Subject: [PATCH 11/24] feat: disable ui elements while syncing --- internal/state.go | 5 +++++ ui/status.go | 12 ++++++++++-- ui/viewport.go | 31 +++++++++++++++++-------------- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/internal/state.go b/internal/state.go index c20508c0..b2ad4344 100644 --- a/internal/state.go +++ b/internal/state.go @@ -62,6 +62,11 @@ func (s *StateModel) Watch(cb func(model *StateModel, err error), ctx context.Co // Fetch Keys s.UpdateKeys(ctx, client) + if s.Status.State == "SYNCING" { + lastRound = s.Status.LastRound + cb(s, nil) + continue + } // Run Round Averages and RX/TX every 5 rounds if s.Status.LastRound%5 == 0 { bm, err := GetBlockMetrics(ctx, client, s.Status.LastRound, s.Metrics.Window) diff --git a/ui/status.go b/ui/status.go index 281288bf..78036aa9 100644 --- a/ui/status.go +++ b/ui/status.go @@ -69,13 +69,21 @@ func (m StatusViewModel) View() string { // Last Round row1 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) - beginning = style.Blue.Render(" Round time: ") + fmt.Sprintf("%.2fs", float64(m.Data.Metrics.RoundTime)/float64(time.Second)) + roundTime := fmt.Sprintf("%.2fs", float64(m.Data.Metrics.RoundTime)/float64(time.Second)) + if m.Data.Status.State == "SYNCING" { + roundTime = "--" + } + beginning = style.Blue.Render(" Round time: ") + roundTime end = fmt.Sprintf("%d KB/s ", m.Data.Metrics.TX/1024) + style.Green.Render("TX ") middle = strings.Repeat(" ", max(0, size-(lipgloss.Width(beginning)+lipgloss.Width(end)+2))) row2 := lipgloss.JoinHorizontal(lipgloss.Left, beginning, middle, end) - beginning = style.Blue.Render(" TPS: ") + fmt.Sprintf("%.2f", m.Data.Metrics.TPS) + tps := fmt.Sprintf("%.2f", m.Data.Metrics.TPS) + if m.Data.Status.State == "SYNCING" { + tps = "--" + } + beginning = style.Blue.Render(" TPS: ") + tps end = fmt.Sprintf("%d KB/s ", m.Data.Metrics.RX/1024) + style.Green.Render("RX ") middle = strings.Repeat(" ", max(0, size-(lipgloss.Width(beginning)+lipgloss.Width(end)+2))) diff --git a/ui/viewport.go b/ui/viewport.go index 660c73cf..9f87aa8b 100644 --- a/ui/viewport.go +++ b/ui/viewport.go @@ -123,7 +123,7 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } if m.page == KeysPage { selKey := m.keysPage.SelectedKey() - if selKey != nil { + if selKey != nil && m.Data.Status.State != "SYNCING" { m.page = TransactionPage return m, keys.EmitKeySelected(selKey) } @@ -143,21 +143,24 @@ func (m ViewportViewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, nil case "t": - if m.page == AccountsPage { - acct := m.accountsPage.SelectedAccount() - data := *m.Data.ParticipationKeys - for i, key := range data { - if key.Address == acct.Address { - m.page = TransactionPage - return m, keys.EmitKeySelected(&data[i]) + if m.Data.Status.State != "SYNCING" { + + if m.page == AccountsPage { + acct := m.accountsPage.SelectedAccount() + data := *m.Data.ParticipationKeys + for i, key := range data { + if key.Address == acct.Address { + m.page = TransactionPage + return m, keys.EmitKeySelected(&data[i]) + } } } - } - if m.page == KeysPage { - selKey := m.keysPage.SelectedKey() - if selKey != nil { - m.page = TransactionPage - return m, keys.EmitKeySelected(selKey) + if m.page == KeysPage { + selKey := m.keysPage.SelectedKey() + if selKey != nil { + m.page = TransactionPage + return m, keys.EmitKeySelected(selKey) + } } } return m, nil From 5615e87a325ed5e82efa30cfbff05f0de61102ec Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 4 Nov 2024 16:02:41 -0500 Subject: [PATCH 12/24] feat: skip account loading on syncing feat: remove offline account expires date --- internal/accounts.go | 25 +++++++++++++++---------- ui/pages/accounts/controller.go | 2 +- ui/pages/accounts/model.go | 26 +++++++++++++++++--------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/internal/accounts.go b/internal/accounts.go index 7169f828..b7492b03 100644 --- a/internal/accounts.go +++ b/internal/accounts.go @@ -121,18 +121,23 @@ func AccountsFromState(state *StateModel, t Time, client *api.ClientWithResponse for _, key := range *state.ParticipationKeys { val, ok := values[key.Address] if !ok { - - account, err := GetAccount(client, key.Address) - - // TODO: handle error - if err != nil { - // TODO: Logging - panic(err) + var account = api.Account{ + Address: key.Address, + Status: "Unknown", + Amount: 0, } - - var expires = t.Now() + if state.Status.State != "SYNCING" { + var err error + account, err = GetAccount(client, key.Address) + // TODO: handle error + if err != nil { + // TODO: Logging + panic(err) + } + } + now := t.Now() + var expires = now.Add(-(time.Hour * 24 * 365 * 100)) if key.EffectiveLastValid != nil { - now := t.Now() roundDiff := max(0, *key.EffectiveLastValid-int(state.Status.LastRound)) distance := int(state.Metrics.RoundTime) * roundDiff expires = now.Add(time.Duration(distance)) diff --git a/ui/pages/accounts/controller.go b/ui/pages/accounts/controller.go index 139ee6dc..4e4cf942 100644 --- a/ui/pages/accounts/controller.go +++ b/ui/pages/accounts/controller.go @@ -21,7 +21,7 @@ func (m ViewModel) HandleMessage(msg tea.Msg) (ViewModel, tea.Cmd) { switch msg := msg.(type) { case internal.StateModel: - m.Data = msg.Accounts + m.Data = &msg m.table.SetRows(*m.makeRows()) case tea.KeyMsg: switch msg.String() { diff --git a/ui/pages/accounts/model.go b/ui/pages/accounts/model.go index 425adf40..91b550cb 100644 --- a/ui/pages/accounts/model.go +++ b/ui/pages/accounts/model.go @@ -4,6 +4,7 @@ import ( "github.com/algorandfoundation/hack-tui/ui/style" "sort" "strconv" + "time" "github.com/algorandfoundation/hack-tui/internal" "github.com/charmbracelet/bubbles/table" @@ -13,7 +14,7 @@ import ( type ViewModel struct { Width int Height int - Data map[string]internal.Account + Data *internal.StateModel table table.Model navigation string @@ -24,7 +25,7 @@ func New(state *internal.StateModel) ViewModel { m := ViewModel{ Width: 0, Height: 0, - Data: state.Accounts, + Data: state, controls: "( (g)enerate )", navigation: "| " + style.Green.Render("(a)ccounts") + " | (k)eys | (t)xn |", } @@ -52,7 +53,7 @@ func (m ViewModel) SelectedAccount() internal.Account { var account internal.Account var selectedRow = m.table.SelectedRow() if selectedRow != nil { - account = m.Data[selectedRow[0]] + account = m.Data.Accounts[selectedRow[0]] } return account } @@ -70,13 +71,20 @@ func (m ViewModel) makeColumns(width int) []table.Column { func (m ViewModel) makeRows() *[]table.Row { rows := make([]table.Row, 0) - for key := range m.Data { + for key := range m.Data.Accounts { + expires := m.Data.Accounts[key].Expires.String() + if m.Data.Status.State == "SYNCING" { + expires = "SYNCING" + } + if !m.Data.Accounts[key].Expires.After(time.Now().Add(-(time.Hour * 24 * 365 * 50))) { + expires = "NA" + } rows = append(rows, table.Row{ - m.Data[key].Address, - strconv.Itoa(m.Data[key].Keys), - m.Data[key].Status, - m.Data[key].Expires.String(), - strconv.Itoa(m.Data[key].Balance), + m.Data.Accounts[key].Address, + strconv.Itoa(m.Data.Accounts[key].Keys), + m.Data.Accounts[key].Status, + expires, + strconv.Itoa(m.Data.Accounts[key].Balance), }) } sort.SliceStable(rows, func(i, j int) bool { From a0abfe6a1bf9ad0a98ceb89140fd3e51ee971d74 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 5 Nov 2024 14:59:59 -0500 Subject: [PATCH 13/24] fix: bit rate display for GB --- ui/status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/status.go b/ui/status.go index fd9cce2a..451a5758 100644 --- a/ui/status.go +++ b/ui/status.go @@ -54,7 +54,7 @@ func getBitRate(bytes int) string { txString = fmt.Sprintf("%d MB/s ", bytes/(1<<20)) } if bytes >= int(math.Pow(1024, 3)) { - txString = fmt.Sprintf("%d GB/s ", bytes/(1<<20)) + txString = fmt.Sprintf("%d GB/s ", bytes/(1<<30)) } return txString From 8ceaebeaf09f7361cdfb23ea6a1bed8c2dd54c67 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 5 Nov 2024 15:48:39 -0500 Subject: [PATCH 14/24] fix: loading of custom endpoint address --- cmd/root.go | 4 ++-- cmd/root_test.go | 14 ++++++++++++++ .../Test_InitConfigWithAddress/algod.admin.token | 1 + .../Test_InitConfigWithAddress/config.json | 4 ++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 cmd/testdata/Test_InitConfigWithAddress/algod.admin.token create mode 100644 cmd/testdata/Test_InitConfigWithAddress/config.json diff --git a/cmd/root.go b/cmd/root.go index 8e4402ca..7d1ecf32 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -214,7 +214,7 @@ func initConfig() { // Check for endpoint address if hasWildcardEndpointUrl(algodConfig.EndpointAddress) { algodConfig.EndpointAddress = replaceEndpointUrl(algodConfig.EndpointAddress) - } else { + } else if algodConfig.EndpointAddress == "" { // Assume it is not set, try to discover the port from the network file networkPath := algorandData + "/algod.net" networkFile, err := os.Open(networkPath) @@ -245,7 +245,7 @@ func initConfig() { } // Set the server configuration - viper.Set("server", "http://"+algodConfig.EndpointAddress) + viper.Set("server", "http://"+strings.Replace(algodConfig.EndpointAddress, "\n", "", 1)) viper.Set("data", dataConfigPath) } diff --git a/cmd/root_test.go b/cmd/root_test.go index cff94e1a..9ca04f90 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -48,3 +48,17 @@ func Test_InitConfigWithoutEndpoint(t *testing.T) { t.Fatal("Invalid Server") } } + +func Test_InitConfigWithAddress(t *testing.T) { + cwd, _ := os.Getwd() + t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithAddress") + + initConfig() + server := viper.Get("server") + if server == "" { + t.Fatal("Invalid Server") + } + if server != "http://255.255.255.255:8080" { + t.Fatal("Invalid Server") + } +} diff --git a/cmd/testdata/Test_InitConfigWithAddress/algod.admin.token b/cmd/testdata/Test_InitConfigWithAddress/algod.admin.token new file mode 100644 index 00000000..71b7a719 --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithAddress/algod.admin.token @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/cmd/testdata/Test_InitConfigWithAddress/config.json b/cmd/testdata/Test_InitConfigWithAddress/config.json new file mode 100644 index 00000000..f172cd52 --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithAddress/config.json @@ -0,0 +1,4 @@ +{ + "EndpointAddress": "255.255.255.255:8080", + "OtherKey": "" +} \ No newline at end of file From a12872dda600f3fc173d444434823a3db76b3eb6 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 5 Nov 2024 16:06:08 -0500 Subject: [PATCH 15/24] fix: loading default port --- cmd/root.go | 4 +++- cmd/root_test.go | 14 ++++++++++++++ .../algod.admin.token | 1 + .../config.json | 4 ++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/algod.admin.token create mode 100644 cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/config.json diff --git a/cmd/root.go b/cmd/root.go index 7d1ecf32..f50bc7a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -230,7 +230,9 @@ func initConfig() { } } - + if strings.Contains(algodConfig.EndpointAddress, ":0") { + algodConfig.EndpointAddress = strings.Replace(algodConfig.EndpointAddress, ":0", ":8080", 1) + } if loadedToken == "" { // Handle Token Path tokenPath := algorandData + "/algod.admin.token" diff --git a/cmd/root_test.go b/cmd/root_test.go index 9ca04f90..c1b3eb4a 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -62,3 +62,17 @@ func Test_InitConfigWithAddress(t *testing.T) { t.Fatal("Invalid Server") } } + +func Test_InitConfigWithAddressAndDefaultPort(t *testing.T) { + cwd, _ := os.Getwd() + t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithAddressAndDefaultPort") + + initConfig() + server := viper.Get("server") + if server == "" { + t.Fatal("Invalid Server") + } + if server != "http://255.255.255.255:8080" { + t.Fatal("Invalid Server") + } +} diff --git a/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/algod.admin.token b/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/algod.admin.token new file mode 100644 index 00000000..71b7a719 --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/algod.admin.token @@ -0,0 +1 @@ +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \ No newline at end of file diff --git a/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/config.json b/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/config.json new file mode 100644 index 00000000..0e455f6b --- /dev/null +++ b/cmd/testdata/Test_InitConfigWithAddressAndDefaultPort/config.json @@ -0,0 +1,4 @@ +{ + "EndpointAddress": "255.255.255.255:0", + "OtherKey": "" +} \ No newline at end of file From 0f3cbb7b5aafb5307e5324d82c009710feb9ae32 Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Tue, 5 Nov 2024 16:09:07 -0500 Subject: [PATCH 16/24] test: clear viper settings --- cmd/root_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/root_test.go b/cmd/root_test.go index c1b3eb4a..7cae6f28 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -37,6 +37,8 @@ func Test_InitConfig(t *testing.T) { func Test_InitConfigWithoutEndpoint(t *testing.T) { cwd, _ := os.Getwd() + viper.Set("token", "") + viper.Set("server", "") t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithoutEndpoint") initConfig() @@ -51,6 +53,8 @@ func Test_InitConfigWithoutEndpoint(t *testing.T) { func Test_InitConfigWithAddress(t *testing.T) { cwd, _ := os.Getwd() + viper.Set("token", "") + viper.Set("server", "") t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithAddress") initConfig() @@ -65,6 +69,8 @@ func Test_InitConfigWithAddress(t *testing.T) { func Test_InitConfigWithAddressAndDefaultPort(t *testing.T) { cwd, _ := os.Getwd() + viper.Set("token", "") + viper.Set("server", "") t.Setenv("ALGORAND_DATA", cwd+"/testdata/Test_InitConfigWithAddressAndDefaultPort") initConfig() From 194461dc38388716ec9b252bbcb1f5ea64fe3cfc Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:39:16 +0100 Subject: [PATCH 17/24] README: Add alpha/dev warnings & notes --- README.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4e0418b1..57140591 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,13 @@ --- Terminal UI for managing Algorand nodes. -Built with [bubbles](https://github.com/charmbracelet/bubbles)/[bubbletea](https://github.com/charmbracelet/bubbletea) +Built with [bubbles](https://github.com/charmbracelet/bubbles) & [bubbletea](https://github.com/charmbracelet/bubbletea) + +> [!CAUTION] +> This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes. + +> [!NOTE] +> We do not have pre-built binaries yet. If you are comfortable doing so, you are welcome to build it yourself and provide feedback. # 🚀 Get Started @@ -31,7 +37,7 @@ Run the build or ~~download the latest cli(WIP)~~. Clone the repository ```bash -git clone git@github.com:algorandfoundation/hack-tui.git +git clone https://github.com/algorandfoundation/hack-tui.git ``` Change to the project directory @@ -48,6 +54,10 @@ make build Start a participation node +> [!NOTE] +> The docker image is used for development and testing purposes. TUI will also work with native algod. +> If you have a node installed already, you can skip this step. + ```bash docker compose up ``` @@ -58,6 +68,9 @@ Connect to the node ./bin/algorun --server http://localhost:8080 --token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ``` +> [!NOTE] +> If you skipped the docker container, try running `./bin/algorun` standalone, which will detect your algorand data directory from the `ALGORAND_DATA` environment variable that works for `goal`. Otherwise, provide the `--server` and `--token` arguments so that it can find your node. + # ℹ️ Usage ## ⚙️ Configuration From f77f9724d54fe7ecb52d17420b1a63c2b711dd53 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:41:30 +0100 Subject: [PATCH 18/24] README: Moved note --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57140591..3d405b1c 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ Built with [bubbles](https://github.com/charmbracelet/bubbles) & [bubbletea](htt > [!CAUTION] > This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes. -> [!NOTE] -> We do not have pre-built binaries yet. If you are comfortable doing so, you are welcome to build it yourself and provide feedback. - # 🚀 Get Started Run the build or ~~download the latest cli(WIP)~~. +> [!NOTE] +> We do not have pre-built binaries yet. If you are comfortable doing so, you are welcome to build it yourself and provide feedback. + ## Building Clone the repository From bed369f98108f92c3838b11e2e30a1f18a695278 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:42:44 +0100 Subject: [PATCH 19/24] README: added note --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 3d405b1c..77d99825 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,9 @@ Connect to the node > [!NOTE] > If you skipped the docker container, try running `./bin/algorun` standalone, which will detect your algorand data directory from the `ALGORAND_DATA` environment variable that works for `goal`. Otherwise, provide the `--server` and `--token` arguments so that it can find your node. +> [!CAUTION] +> This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes connected to public networks. + # ℹ️ Usage ## ⚙️ Configuration From 6e5d200137c82c1fb62c0cb698e87dd3dfc133a9 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:45:35 +0100 Subject: [PATCH 20/24] README: added note about admin token --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 77d99825..60d70313 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,9 @@ The application supports the `server` and `token` flags for configuration. algorun --server http://localhost:8080 --token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ``` +> [!IMPORTANT] +> TUI requires the *admin* token in order to access participation key information. This can be found in the `algod.admin.token` file, e.g. `/var/lib/algorand/algod.admin.token` + ## 🧑‍💻 Commands The default command will launch the full TUI application From fac0adb0d6843a5cfb1b5776cb6a48b4d506e1c2 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:47:39 +0100 Subject: [PATCH 21/24] README: reordered --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 60d70313..ec7834a0 100644 --- a/README.md +++ b/README.md @@ -54,26 +54,26 @@ make build Start a participation node -> [!NOTE] -> The docker image is used for development and testing purposes. TUI will also work with native algod. -> If you have a node installed already, you can skip this step. - ```bash docker compose up ``` +> [!NOTE] +> The docker image is used for development and testing purposes. TUI will also work with native algod. +> If you have a node installed already, you can skip this step. + Connect to the node ```bash ./bin/algorun --server http://localhost:8080 --token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ``` -> [!NOTE] -> If you skipped the docker container, try running `./bin/algorun` standalone, which will detect your algorand data directory from the `ALGORAND_DATA` environment variable that works for `goal`. Otherwise, provide the `--server` and `--token` arguments so that it can find your node. - > [!CAUTION] > This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes connected to public networks. +> [!NOTE] +> If you skipped the docker container, try running `./bin/algorun` standalone, which will detect your algorand data directory from the `ALGORAND_DATA` environment variable that works for `goal`. Otherwise, provide the `--server` and `--token` arguments so that it can find your node. Note that algorun requires the admin algod token. + # ℹ️ Usage ## ⚙️ Configuration From 26825f7c419d6a4c2b4eda5064ac4f4f9ff97ff1 Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:48:40 +0100 Subject: [PATCH 22/24] README: numbered build steps --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ec7834a0..529e672d 100644 --- a/README.md +++ b/README.md @@ -34,25 +34,25 @@ Run the build or ~~download the latest cli(WIP)~~. ## Building -Clone the repository +1. Clone the repository ```bash git clone https://github.com/algorandfoundation/hack-tui.git ``` -Change to the project directory +2. Change to the project directory ```bash cd hack-tui ``` -Run the build command +3. Run the build command ```bash make build ``` -Start a participation node +4. Start a participation node ```bash docker compose up @@ -62,7 +62,7 @@ docker compose up > The docker image is used for development and testing purposes. TUI will also work with native algod. > If you have a node installed already, you can skip this step. -Connect to the node +5. Connect to the node ```bash ./bin/algorun --server http://localhost:8080 --token aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa From ee453065464fc92115bd017f0e35ef3f7e3f4bfe Mon Sep 17 00:00:00 2001 From: Tasos Bitsios Date: Wed, 13 Nov 2024 10:53:16 +0100 Subject: [PATCH 23/24] README: final note --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 529e672d..d419c150 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Terminal UI for managing Algorand nodes. Built with [bubbles](https://github.com/charmbracelet/bubbles) & [bubbletea](https://github.com/charmbracelet/bubbletea) > [!CAUTION] -> This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes. +> This project is in alpha state and under heavy development. We do not recommend performing actions (e.g. key management) on participation nodes connected to public networks. # 🚀 Get Started From 39436426b5594c0ca1d8acf1a39595ce3b9527ba Mon Sep 17 00:00:00 2001 From: Michael Feher Date: Mon, 18 Nov 2024 10:10:00 -0500 Subject: [PATCH 24/24] test: add codecov reporter --- .github/workflows/test.yaml | 8 ++++---- .testcoverage.yaml | 26 -------------------------- 2 files changed, 4 insertions(+), 30 deletions(-) delete mode 100644 .testcoverage.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2e8bb1a4..3cad3ebb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -56,9 +56,9 @@ jobs: run: go build -o bin/algorun *.go - name: Test with the Go CLI - run: go test ./... -coverprofile=./cover.out -covermode=atomic -coverpkg=./... + run: go test ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./... - - name: Check test coverage - uses: vladopajic/go-test-coverage@v2 + - name: Upload results to Codecov + uses: codecov/codecov-action@v4 with: - config: ./.testcoverage.yaml + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.testcoverage.yaml b/.testcoverage.yaml deleted file mode 100644 index 18363129..00000000 --- a/.testcoverage.yaml +++ /dev/null @@ -1,26 +0,0 @@ -profile: cover.out - -# (optional; but recommended to set) -# When specified reported file paths will not contain local prefix in the output -local-prefix: "github.com/algorandfoundation/hack-tui" - -# Holds coverage thresholds percentages, values should be in range [0-100] -threshold: - # (optional; default 0) - # The minimum coverage that each file should have - file: 0 - - # (optional; default 0) - # The minimum coverage that each package should have - package: 0 - - # (optional; default 0) - # The minimum total coverage project should have - total: 0 - -# Holds regexp rules which will exclude matched files or packages -# from coverage statistics -exclude: - # Exclude files or packages matching their paths - paths: - - api/if.go