This commit is contained in:
jms 2020-11-08 19:37:07 -06:00
parent cad1ad2fe2
commit 05ed1f210d
14 changed files with 204 additions and 264 deletions

View File

@ -102,10 +102,7 @@ to quickly create a Cobra application.`,
db = database.New(&data) db = database.New(&data)
// Initialise the Gui / Tui // Initialise the Gui / Tui
gui := gui.New(db) gui.Gui(db)
if err := gui.Start(); err != nil {
log.Fatalf("main: Cannot start tui: %s", err)
}
} }
}, },

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/charmbracelet/bubbletea v0.12.2 github.com/charmbracelet/bubbletea v0.12.2
github.com/gdamore/tcell v1.2.0 github.com/gdamore/tcell v1.2.0
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1
github.com/gizak/termui/v3 v3.1.0
github.com/idlephysicist/cave-logger v1.2.2 github.com/idlephysicist/cave-logger v1.2.2
github.com/lib/pq v1.8.0 github.com/lib/pq v1.8.0
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0

8
go.sum
View File

@ -55,6 +55,9 @@ github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 h1:gp9ujdOQmQf1gMvqOYYgxdMS5tRpRGE3HAgRH4Hgzd4= github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 h1:gp9ujdOQmQf1gMvqOYYgxdMS5tRpRGE3HAgRH4Hgzd4=
github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gizak/termui v3.1.0+incompatible h1:N3CFm+j087lanTxPpHOmQs0uS3s5I9TxoAFy6DqPqv8=
github.com/gizak/termui/v3 v3.1.0 h1:ZZmVDgwHl7gR7elfKf1xc4IudXZ5qqfDh4wExk4Iajc=
github.com/gizak/termui/v3 v3.1.0/go.mod h1:bXQEBkJpzxUAKf0+xq9MSWAvWZlE7c+aidmyFlkYTrY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -131,6 +134,7 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
@ -141,6 +145,8 @@ github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceT
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -151,6 +157,8 @@ github.com/muesli/termenv v0.7.2/go.mod h1:ct2L5N2lmix82RaY3bMWwVu/jUFc9Ule0KGDC
github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8= github.com/muesli/termenv v0.7.4 h1:/pBqvU5CpkY53tU0vVn+xgs2ZTX63aH5nY+SSps5Xa8=
github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc= github.com/muesli/termenv v0.7.4/go.mod h1:pZ7qY9l3F7e5xsAOS0zCew2tME+p7bWeBkotCEcIIcc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d h1:x3S6kxmy49zXVVyhcnrFqxvNVCBPb2KZ9hV2RBdS840=
github.com/nsf/termbox-go v0.0.0-20190121233118-02980233997d/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=

View File

@ -60,7 +60,7 @@ func (db *Database) UserSessions() ([]*model.Session, error) {
var sessCount model.Session var sessCount model.Session
var l logger.Log var l logger.Log
err = rows.Scan(&sessCount.Username, &sessCount.Count) err = rows.Scan(&sessCount.Username, &sessCount.Count)
l = logger.Log{"info", logrus.Fields{"session" : sessCount.Count}, " count " + sessCount.Username + " users on host"} l = logger.Log{"info", logrus.Fields{"session" : sessCount.Count, "users" : sessCount.Username}, " count " + sessCount.Username + " users on host"}
logger.Lgr(&l) logger.Lgr(&l)
if err != nil { if err != nil {
// handle this error // handle this error
@ -77,4 +77,27 @@ func (db *Database) UserSessions() ([]*model.Session, error) {
return sessions, err return sessions, err
}
func (db *Database) ConnUse() (*model.Guage, error) {
var i int
var i1 int
var m model.Guage
query := `SELECT sum(numbackends) FROM pg_stat_database;`
q2 := `show max_connections`
err := db.conn.QueryRow(query).Scan(&i)
if err != nil {
// handle this error better than this
panic(err)
}
err2 := db.conn.QueryRow(q2).Scan(&i1)
if err2 != nil {
// handle this error better than this
panic(err)
}
m = model.Guage{i, i1}
return &m, nil
} }

View File

@ -1,4 +1,13 @@
[sessions.Current] [sessions.Current.table]
columns = 2 headers = ["users", "count sessions"]
headers = ["users", "count users"] query = "select usename, count(usename) from pg_stat_activity where usename is not null"
query = "select usename, count(usename) from pg_stat_activity where usename is not null" [sessions.Query.table]
headers = ["query", "username"]
query = "SELECT query ,usename FROM pg_stat_activity where query is not null and usename is not null and state != 'idle';"
[sessions.Limit.guage]
headers = ["used", "total"]
q1 = "SHOW max_connections"
q2 = "SELECT sum(numbackends) FROM pg_stat_database"
[sessions.identification.box]
widget = "box"
q = "SELECT current_database();"

View File

@ -1,120 +1,132 @@
package gui package gui
import ( import (
"fmt"
ui "github.com/gizak/termui/v3"
"github.com/gizak/termui/v3/widgets"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"log"
"shiny-pancake/internal/database"
"shiny-pancake/internal/model" "shiny-pancake/internal/model"
"shiny-pancake/logger" "shiny-pancake/logger"
"shiny-pancake/internal/database"
"strings"
"github.com/rivo/tview"
) )
type resources struct { func loadModels(grid *ui.Grid, s *model.Screen){
ActSess []*model.Session _ = grid
} // try to force two rows when possible.
// max 3 columns
for _, i := range s.Queries {
type panels struct { l := logger.Log{"info", logrus.Fields{"query" : i}, " query for session"}
currentPanel int logger.Lgr(&l)
panel []panel switch s.Queries {
} case s.Queries
type state struct {
panels panels
tabBar *tview.TextView
resources resources
stopChans map[string]chan int
}
func newState() *state {
return &state{
stopChans: make(map[string]chan int),
}
}
type Gui struct {
app *tview.Application
pages *tview.Pages
state *state
db *database.Database
}
func New(db *database.Database) *Gui {
return &Gui{
app: tview.NewApplication(),
pages: tview.NewPages(),
state: newState(),
db: db,
}
}
func (g *Gui) sessionPanel() *activeSessions {
for _, panel := range g.state.panels.panel {
if panel.name() == `activeSessions` {
return panel.(*activeSessions)
} }
} }
return nil
} }
// Start start application
func (g *Gui) Start() error { func Gui(db *database.Database) {
g.initPanels() if err := ui.Init(); err != nil {
g.startMonitoring() log.Fatalf("failed to initialize termui: %v", err)
var l logger.Log
l = logger.Log{"info", logrus.Fields{"init" : "ran"}, "got this far"}
logger.Lgr(&l)
if err := g.app.Run(); err != nil {
g.app.Stop()
return err
} }
defer ui.Close()
tabpane := widgets.NewTabPane("sessions", "disk", "")
tabpane.SetRect(0, 1, 100, 4)
tabpane.Border = false
var session *model.Screen
session = Session()
grid := ui.NewGrid()
loadModels(grid, session)
//termWidth, termHeight := ui.TerminalDimensions()
//grid.SetRect(0, 0, termWidth, termHeight)
//
//grid.Set(
// ui.NewRow(1.0/2,
// ui.NewCol(1.0/2, tabpane),
//
// ),
//)
//
//ui.Render(grid)
//tickerCount := 1
//uiEvents := ui.PollEvents()
//ticker := time.NewTicker(time.Second).C
//for {
// select {
// case e := <-uiEvents:
// switch e.ID {
// case "q", "<C-c>":
// return
// case "<Resize>":
// payload := e.Payload.(ui.Resize)
// grid.SetRect(0, 0, payload.Width, payload.Height)
// ui.Clear()
// ui.Render(grid)
// }
// case <-ticker:
// if tickerCount == 100 {
// return
// }
// ui.Render(grid)
// tickerCount++
// }
//}
return nil
} }
func (g *Gui) Stop() {
g.stopMonitoring()
g.app.Stop()
}
// Page "definitions"
func (g *Gui) initPanels() {
g.state.tabBar = newTabBar()
// Page definitions
s := newSessions(g)
var l logger.Log
l = logger.Log{"info", logrus.Fields{"initPanels" : "ran"}, "got this far"}
logger.Lgr(&l)
g.pages.AddPage(`activeSessions`, s, true, true)
fmt.Fprintf(g.state.tabBar, ` ["%d"]%d %s[""] `, 0, 1, strings.Title(s.name()))
g.state.tabBar.Highlight("0")
// Panels
g.state.panels.panel = append(g.state.panels.panel, s)
layout := tview.NewFlex().SetDirection(tview.FlexRow).
AddItem(g.state.tabBar, 1, 1, false).
AddItem(g.pages, 0, 16, true)
g.app.SetRoot(layout, true)
g.goTo(`activeSessions`)
}
func (g *Gui) goTo(page string) {
g.pages.SwitchToPage(page)
}
//func placeholder(){
// _ = db
// if err := ui.Init(); err != nil {
// log.Fatalf("failed to initialize termui: %v", err)
// }
// defer ui.Close()
// sessions, err := db.UserSessions()
// if err != nil {
// log.Fatal(err)
// }
// table1 := widgets.NewTable()
// table1.Rows = [][]string{
// []string{"user", "count sessions"},
// }
// table1.TextAlignment = ui.AlignLeft
//
// for _, s := range sessions {
// var sd []string
// sd = append(sd, s.Username)
// sd = append(sd, strconv.Itoa(s.Count))
// table1.Rows = append(table1.Rows, sd)
// }
//
// table1.TextStyle = ui.NewStyle(ui.ColorWhite)
// table1.SetRect(0, 0, 60, 10)
// table1.Title = "user sessions"
// connsUsed, err := db.ConnUse()
// if err != nil {
// log.Fatal()
// }
// g3 := widgets.NewGauge()
// g3.Title = "connections used"
// g3.SetRect(0, 11, 50, 14)
// percent := float64(connsUsed.I)/float64(connsUsed.I1)
// l := logger.Log{"info", logrus.Fields{"countOpenConn": strconv.Itoa(connsUsed.I), "countMaxConn": strconv.Itoa(connsUsed.I1) }, "count of conns"}
// logger.Lgr(&l)
// g3.Percent = int(percent)
// g3.BarColor = ui.ColorGreen
// g3.LabelStyle = ui.NewStyle(ui.ColorYellow)
// g3.Label = fmt.Sprintf("%d of %d total", g3.Percent, connsUsed.I1)
//
// ui.Render(table1)
// ui.Render(g3)
//
//
// uiEvents := ui.PollEvents()
// for {
// e := <-uiEvents
// switch e.ID {
// case "q", "<C-c>":
// return
// }
// }
//}

View File

@ -1,18 +0,0 @@
package gui
func (g *Gui) startMonitoring() {
stop := make(chan int, 1)
g.state.stopChans["activeSessions"] = stop
go g.sessionPanel().monitoringSessions(g)
}
func (g *Gui) stopMonitoring() {
g.state.stopChans["activeSessions"] <- 1
}
/*func (g *Gui) updateTask() {
g.app.QueueUpdateDraw(func() {
g.caversPanel().setEntries(g) // REVIEW: Why is this just people ?
})
}*/

View File

@ -1,10 +0,0 @@
package gui
type panel interface {
name() string
entries(*Gui)
setEntries(*Gui)
updateEntries(*Gui)
focus(*Gui)
unfocus()
}

View File

@ -1,108 +0,0 @@
package gui
import (
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
"shiny-pancake/internal/model"
"time"
)
type activeSessions struct {
*tview.Table
sn chan *model.Session
filterCol, filterTerm string
}
func newSessions(g *Gui) *activeSessions {
sess := &activeSessions{
Table: tview.NewTable(),
sn: make(chan *model.Session),
}
sess.SetBorder(true)
sess.entries(g)
return sess
}
func (t *activeSessions) name() string {
return `activeSessions`
}
func (t *activeSessions) entries(g *Gui) {
sss, err := g.db.UserSessions()
if err != nil {
return
}
g.state.resources.ActSess = sss
}
func (t *activeSessions) setEntries(g *Gui) {
t.entries(g)
table := t.Clear()
headers := []string{
"user",
"session count",
}
for i, header := range headers {
table.SetCell(0, i, &tview.TableCell{
Text: header,
NotSelectable: true,
Align: tview.AlignLeft,
Color: tview.Styles.PrimaryTextColor,
BackgroundColor: tview.Styles.PrimitiveBackgroundColor,
Attributes: tcell.AttrBold,
})
}
for i, s := range g.state.resources.ActSess {
table.SetCell(i+1, 0, tview.NewTableCell(s.Username).
SetTextColor(tview.Styles.PrimaryTextColor).
SetMaxWidth(30).
SetExpansion(1))
table.SetCell(i+1, 1, tview.NewTableCell(string(s.Count)).
SetTextColor(tview.Styles.PrimaryTextColor).
SetMaxWidth(30).
SetExpansion(1))
}
}
func (t *activeSessions) updateEntries(g *Gui) {
g.app.QueueUpdateDraw(func() {
t.setEntries(g)
})
}
func (t *activeSessions) focus(g *Gui) {
t.SetSelectable(true, false)
g.app.SetFocus(t)
}
func (t *activeSessions) unfocus() {
t.SetSelectable(false, false)
}
func (t *activeSessions) monitoringSessions(g *Gui) {
ticker := time.NewTicker(5 * time.Minute)
LOOP:
for {
select {
case <-ticker.C:
t.updateEntries(g)
case <-g.state.stopChans["trips"]:
ticker.Stop()
break LOOP
}
}
}

View File

@ -1 +0,0 @@
package gui

View File

@ -1,15 +0,0 @@
package gui
import (
"github.com/rivo/tview"
)
func newTabBar() *tview.TextView {
return tview.NewTextView().
SetDynamicColors(true).
SetRegions(true).
SetWrap(false)/*.
SetHighlightedFunc(func(added, removed, remaining []string) {
g.pages.SwitchToPage(added[0])
})*/
}

14
internal/gui/tabs.go Normal file
View File

@ -0,0 +1,14 @@
package gui
import "shiny-pancake/internal/model"
func Session() *model.Screen{
var sessions model.Screen
sessions = model.Screen{
4,
[]string{"sessions.Limit", "sessions.Query", "sessions.Current", "sessions.identification"},
}
return &sessions
}

View File

@ -11,3 +11,31 @@ type Session struct {
Username string Username string
Count int Count int
} }
type Guage struct {
I int
I1 int
}
type Table struct {
Headers []string
Q string
Rows [][]string
}
type TextBox struct {
Q string
Content []string
}
type Screen struct {
Count int
Queries []string
}
type Identification struct {
Dbname string
PGVersion string
DbQuery string
VersionQuery string
}

BIN
main

Binary file not shown.