mirror of https://github.com/edouardparis/lntop
Merge pull request #101 from hieblmi/forwarding-history
feature: A new forwarding history tabpull/106/head
commit
c6748d321f
@ -0,0 +1,17 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type ForwardingEvent struct {
|
||||
PeerAliasIn string
|
||||
PeerAliasOut string
|
||||
ChanIdIn uint64
|
||||
ChanIdOut uint64
|
||||
AmtIn uint64
|
||||
AmtOut uint64
|
||||
Fee uint64
|
||||
FeeMsat uint64
|
||||
AmtInMsat uint64
|
||||
AmtOutMsat uint64
|
||||
EventTime time.Time
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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))
|
||||
}
|
Loading…
Reference in New Issue