Forwarding history UI changes

pull/101/head
Slyghtning 2 years ago
parent fa3b92a9a6
commit 4f099e3746

@ -227,6 +227,8 @@ func (c *controller) Order(order models.Order) func(*gocui.Gui, *gocui.View) err
c.views.Channels.Sort("", order)
case views.TRANSACTIONS:
c.views.Transactions.Sort("", order)
case views.FWDINGHIST:
c.views.FwdingHist.Sort("", order)
}
return nil
}
@ -293,6 +295,19 @@ func (c *controller) OnEnter(g *gocui.Gui, v *gocui.View) error {
if err != nil {
return err
}
case views.FWDINGHIST:
err := c.views.Main.Delete(g)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
defer cancel()
c.models.RefreshForwardingHistory(ctx)
c.views.Main = c.views.FwdingHist
err = c.views.FwdingHist.Set(g, 11, 6, maxX-1, maxY)
if err != nil {
return err
}
}
case views.TRANSACTIONS:

@ -0,0 +1,72 @@
package models
import (
"sort"
"sync"
"github.com/edouardparis/lntop/network/models"
)
type FwdinghistSort func(*models.ForwardingEvent, *models.ForwardingEvent) bool
type FwdingHist struct {
StartTime string
MaxNumEvents uint32
current *models.ForwardingEvent
list []*models.ForwardingEvent
sort FwdinghistSort
mu sync.RWMutex
}
func (t *FwdingHist) Current() *models.ForwardingEvent {
return t.current
}
func (t *FwdingHist) SetCurrent(index int) {
t.current = t.Get(index)
}
func (t *FwdingHist) List() []*models.ForwardingEvent {
return t.list
}
func (t *FwdingHist) Len() int {
return len(t.list)
}
func (t *FwdingHist) Clear() {
t.list = []*models.ForwardingEvent{}
}
func (t *FwdingHist) Swap(i, j int) {
t.list[i], t.list[j] = t.list[j], t.list[i]
}
func (t *FwdingHist) Less(i, j int) bool {
return t.sort(t.list[i], t.list[j])
}
func (t *FwdingHist) Sort(s FwdinghistSort) {
if s == nil {
return
}
t.sort = s
sort.Sort(t)
}
func (t *FwdingHist) Get(index int) *models.ForwardingEvent {
if index < 0 || index > len(t.list)-1 {
return nil
}
return t.list[index]
}
func (t *FwdingHist) Update(events []*models.ForwardingEvent) {
t.mu.Lock()
defer t.mu.Unlock()
t.Clear()
for _, event := range events {
t.list = append(t.list, event)
}
}

@ -2,6 +2,7 @@ package models
import (
"context"
"strconv"
"github.com/edouardparis/lntop/app"
"github.com/edouardparis/lntop/logging"
@ -19,9 +20,27 @@ type Models struct {
ChannelsBalance *ChannelsBalance
Transactions *Transactions
RoutingLog *RoutingLog
FwdingHist *FwdingHist
}
func New(app *app.App) *Models {
fwdingHist := FwdingHist{}
startTime := app.Config.Views.FwdingHist.Options.GetOption("START_TIME", "start_time")
maxNumEvents := app.Config.Views.FwdingHist.Options.GetOption("MAX_NUM_EVENTS", "max_num_events")
if startTime != "" {
fwdingHist.StartTime = startTime
}
if maxNumEvents != "" {
max, err := strconv.ParseUint(maxNumEvents, 10, 32)
if err != nil {
app.Logger.Info("Couldn't parse the maximum number of forwarding events.")
} else {
fwdingHist.MaxNumEvents = uint32(max)
}
}
return &Models{
logger: app.Logger.With(logging.String("logger", "models")),
network: app.Network,
@ -31,6 +50,7 @@ func New(app *app.App) *Models {
ChannelsBalance: &ChannelsBalance{},
Transactions: &Transactions{},
RoutingLog: &RoutingLog{},
FwdingHist: &fwdingHist,
}
}
@ -47,6 +67,21 @@ func (m *Models) RefreshInfo(ctx context.Context) error {
return nil
}
func (m *Models) RefreshForwardingHistory(ctx context.Context) error {
<<<<<<< Updated upstream
forwardingEvents, err := m.network.GetForwardingHistory(ctx)
=======
forwardingEvents, err := m.network.GetForwardingHistory(ctx, m.FwdingHist.StartTime, m.FwdingHist.MaxNumEvents)
>>>>>>> Stashed changes
if err != nil {
return err
}
m.FwdingHist.Update(forwardingEvents)
return nil
}
func (m *Models) RefreshChannels(ctx context.Context) error {
channels, err := m.network.ListChannels(ctx, options.WithChannelPending)
if err != nil {

@ -0,0 +1,418 @@
package views
import (
"bytes"
"fmt"
"github.com/awesome-gocui/gocui"
"golang.org/x/text/language"
"golang.org/x/text/message"
"github.com/edouardparis/lntop/config"
netmodels "github.com/edouardparis/lntop/network/models"
"github.com/edouardparis/lntop/ui/color"
"github.com/edouardparis/lntop/ui/models"
)
const (
FWDINGHIST = "fwdinghist"
FWDINGHIST_COLUMNS = "fwdinghist_columns"
FWDINGHIST_FOOTER = "fwdinghist_footer"
)
var DefaultFwdinghistColumns = []string{
"ALIAS_IN",
"ALIAS_OUT",
"AMT_IN",
"AMT_OUT",
"FEE",
"TIMESTAMP_NS",
"CHAN_ID_IN",
"CHAN_ID_OUT",
}
type FwdingHist struct {
cfg *config.View
columns []fwdinghistColumn
columnHeadersView *gocui.View
view *gocui.View
fwdinghist *models.FwdingHist
ox, oy int
cx, cy int
}
type fwdinghistColumn struct {
name string
width int
sorted bool
sort func(models.Order) models.FwdinghistSort
display func(*netmodels.ForwardingEvent, ...color.Option) string
}
func (c FwdingHist) Index() int {
_, oy := c.view.Origin()
_, cy := c.view.Cursor()
return cy + oy
}
func (c FwdingHist) Name() string {
return FWDINGHIST
}
func (c *FwdingHist) Wrap(v *gocui.View) View {
c.view = v
return c
}
func (c FwdingHist) currentColumnIndex() int {
x := c.ox + c.cx
index := 0
sum := 0
for i := range c.columns {
sum += c.columns[i].width + 1
if x < sum {
return index
}
index++
}
return index
}
func (c FwdingHist) Origin() (int, int) {
return c.ox, c.oy
}
func (c FwdingHist) Cursor() (int, int) {
return c.cx, c.cy
}
func (c *FwdingHist) SetCursor(cx, cy int) error {
if err := cursorCompat(c.columnHeadersView, cx, 0); err != nil {
return err
}
err := c.columnHeadersView.SetCursor(cx, 0)
if err != nil {
return err
}
if err := cursorCompat(c.view, cx, cy); err != nil {
return err
}
err = c.view.SetCursor(cx, cy)
if err != nil {
return err
}
c.cx, c.cy = cx, cy
return nil
}
func (c *FwdingHist) SetOrigin(ox, oy int) error {
err := c.columnHeadersView.SetOrigin(ox, 0)
if err != nil {
return err
}
err = c.view.SetOrigin(ox, oy)
if err != nil {
return err
}
c.ox, c.oy = ox, oy
return nil
}
func (c *FwdingHist) Speed() (int, int, int, int) {
current := c.currentColumnIndex()
up := 0
down := 0
if c.Index() > 0 {
up = 1
}
if c.Index() < c.fwdinghist.Len()-1 {
down = 1
}
if current > len(c.columns)-1 {
return 0, c.columns[current-1].width + 1, down, up
}
if current == 0 {
return c.columns[0].width + 1, 0, down, up
}
return c.columns[current].width + 1,
c.columns[current-1].width + 1,
down, up
}
func (c *FwdingHist) Limits() (pageSize int, fullSize int) {
_, pageSize = c.view.Size()
fullSize = c.fwdinghist.Len()
return
}
func (c *FwdingHist) Sort(column string, order models.Order) {
if column == "" {
index := c.currentColumnIndex()
if index >= len(c.columns) {
return
}
col := c.columns[index]
if col.sort == nil {
return
}
c.fwdinghist.Sort(col.sort(order))
for i := range c.columns {
c.columns[i].sorted = (i == index)
}
}
}
func (c FwdingHist) Delete(g *gocui.Gui) error {
err := g.DeleteView(FWDINGHIST_COLUMNS)
if err != nil {
return err
}
err = g.DeleteView(FWDINGHIST)
if err != nil {
return err
}
return g.DeleteView(FWDINGHIST_FOOTER)
}
func (c *FwdingHist) Set(g *gocui.Gui, x0, y0, x1, y1 int) error {
var err error
setCursor := false
c.columnHeadersView, err = g.SetView(FWDINGHIST_COLUMNS, x0-1, y0, x1+2, y0+2, 0)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
setCursor = true
}
c.columnHeadersView.Frame = false
c.columnHeadersView.BgColor = gocui.ColorGreen
c.columnHeadersView.FgColor = gocui.ColorBlack
c.view, err = g.SetView(FWDINGHIST, x0-1, y0+1, x1+2, y1-1, 0)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
setCursor = true
}
c.view.Frame = false
c.view.Autoscroll = false
c.view.SelBgColor = gocui.ColorCyan
c.view.SelFgColor = gocui.ColorBlack | gocui.AttrDim
c.view.Highlight = true
c.display()
if setCursor {
ox, oy := c.Origin()
err := c.SetOrigin(ox, oy)
if err != nil {
return err
}
cx, cy := c.Cursor()
err = c.SetCursor(cx, cy)
if err != nil {
return err
}
}
footer, err := g.SetView(FWDINGHIST_FOOTER, x0-1, y1-2, x1+2, y1, 0)
if err != nil {
if err != gocui.ErrUnknownView {
return err
}
}
footer.Frame = false
footer.BgColor = gocui.ColorCyan
footer.FgColor = gocui.ColorBlack
footer.Rewind()
blackBg := color.Black(color.Background)
fmt.Fprintln(footer, fmt.Sprintf("%s%s %s%s %s%s",
blackBg("F2"), "Menu",
blackBg("Enter"), "FwdingHist",
blackBg("F10"), "Quit",
))
return nil
}
func (c *FwdingHist) display() {
c.columnHeadersView.Rewind()
var buffer bytes.Buffer
current := c.currentColumnIndex()
for i := range c.columns {
if current == i {
buffer.WriteString(color.Cyan(color.Background)(c.columns[i].name))
buffer.WriteString(" ")
continue
} else if c.columns[i].sorted {
buffer.WriteString(color.Magenta(color.Background)(c.columns[i].name))
buffer.WriteString(" ")
continue
}
buffer.WriteString(c.columns[i].name)
buffer.WriteString(" ")
}
fmt.Fprintln(c.columnHeadersView, buffer.String())
c.view.Rewind()
for _, item := range c.fwdinghist.List() {
var buffer bytes.Buffer
for i := range c.columns {
var opt color.Option
if current == i {
opt = color.Bold
}
buffer.WriteString(c.columns[i].display(item, opt))
buffer.WriteString(" ")
}
fmt.Fprintln(c.view, buffer.String())
}
}
func NewFwdingHist(cfg *config.View, hist *models.FwdingHist) *FwdingHist {
fwdinghist := &FwdingHist{
cfg: cfg,
fwdinghist: hist,
}
printer := message.NewPrinter(language.English)
columns := DefaultFwdinghistColumns
if cfg != nil && len(cfg.Columns) != 0 {
columns = cfg.Columns
}
fwdinghist.columns = make([]fwdinghistColumn, len(columns))
for i := range columns {
switch columns[i] {
case "ALIAS_IN":
fwdinghist.columns[i] = fwdinghistColumn{
width: 30,
name: fmt.Sprintf("%30s", columns[i]),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.StringSort(e1.PeerAliasIn, e2.PeerAliasOut, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(fmt.Sprintf("%30s", e.PeerAliasIn))
},
}
case "ALIAS_OUT":
fwdinghist.columns[i] = fwdinghistColumn{
width: 30,
name: fmt.Sprintf("%30s", columns[i]),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.StringSort(e1.PeerAliasOut, e2.PeerAliasOut, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(fmt.Sprintf("%30s", e.PeerAliasOut))
},
}
case "CHAN_ID_IN":
fwdinghist.columns[i] = fwdinghistColumn{
width: 19,
name: fmt.Sprintf("%19s", columns[i]),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.UInt64Sort(e1.ChanIdIn, e2.ChanIdIn, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(fmt.Sprintf("%19d", e.ChanIdIn))
},
}
case "CHAN_ID_OUT":
fwdinghist.columns[i] = fwdinghistColumn{
width: 19,
name: fmt.Sprintf("%19s", columns[i]),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.UInt64Sort(e1.ChanIdOut, e2.ChanIdOut, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(fmt.Sprintf("%19d", e.ChanIdOut))
},
}
case "AMT_IN":
fwdinghist.columns[i] = fwdinghistColumn{
width: 12,
name: fmt.Sprintf("%12s", "RECEIVED"),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.UInt64Sort(e1.AmtIn, e2.AmtIn, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(printer.Sprintf("%12d", e.AmtIn))
},
}
case "AMT_OUT":
fwdinghist.columns[i] = fwdinghistColumn{
width: 12,
name: fmt.Sprintf("%12s", "SENT"),
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.UInt64Sort(e1.AmtOut, e2.AmtOut, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(printer.Sprintf("%12d", e.AmtOut))
},
}
case "FEE":
fwdinghist.columns[i] = fwdinghistColumn{
name: fmt.Sprintf("%9s", "EARNED"),
width: 9,
sort: func(order models.Order) models.FwdinghistSort {
return func(e1, e2 *netmodels.ForwardingEvent) bool {
return models.UInt64Sort(e1.Fee, e2.Fee, order)
}
},
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return fee(e.Fee)
},
}
case "TIMESTAMP_NS":
fwdinghist.columns[i] = fwdinghistColumn{
name: fmt.Sprintf("%15s", "TIME"),
width: 20,
display: func(e *netmodels.ForwardingEvent, opts ...color.Option) string {
return color.White(opts...)(fmt.Sprintf("%20s", e.EventTime.Format("15:04:05 Jan _2")))
},
}
default:
fwdinghist.columns[i] = fwdinghistColumn{
name: fmt.Sprintf("%-21s", columns[i]),
width: 21,
display: func(tx *netmodels.ForwardingEvent, opts ...color.Option) string {
return "column does not exist"
},
}
}
}
return fwdinghist
}
func fee(fee uint64, opts ...color.Option) string {
if fee >= 0 && fee < 100 {
return color.Cyan(opts...)(fmt.Sprintf("%9d", fee))
} else if fee >= 100 && fee < 999 {
return color.Green(opts...)(fmt.Sprintf("%9d", fee))
}
return color.Yellow(opts...)(fmt.Sprintf("%9d", fee))
}

@ -17,6 +17,7 @@ var menu = []string{
"CHANNEL",
"TRANSAC",
"ROUTING",
"FWDHIST",
}
type Menu struct {
@ -85,6 +86,8 @@ func (h Menu) Current() string {
return TRANSACTIONS
case "ROUTING":
return ROUTING
case "FWDHIST":
return FWDINGHIST
}
}
return ""

@ -31,6 +31,7 @@ type Views struct {
Transactions *Transactions
Transaction *Transaction
Routing *Routing
FwdingHist *FwdingHist
}
func (v Views) Get(vi *gocui.View) View {
@ -50,6 +51,8 @@ func (v Views) Get(vi *gocui.View) View {
return v.Transaction.Wrap(vi)
case ROUTING:
return v.Routing.Wrap(vi)
case FWDINGHIST:
return v.FwdingHist.Wrap(vi)
default:
return nil
}
@ -106,6 +109,7 @@ func New(cfg config.Views, m *models.Models) *Views {
Transactions: NewTransactions(cfg.Transactions, m.Transactions),
Transaction: NewTransaction(m.Transactions),
Routing: NewRouting(cfg.Routing, m.RoutingLog, m.Channels),
FwdingHist: NewFwdingHist(cfg.FwdingHist, m.FwdingHist),
Main: main,
}
}

Loading…
Cancel
Save