diff --git a/cmd/add.go b/cmd/add.go index 964364a..e99a9a3 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -25,7 +25,7 @@ import ( "github.com/spf13/viper" "io/ioutil" "log" - "shiny-pancake/database" + "shiny-pancake/internal/model" "os" "shiny-pancake/logger" @@ -90,7 +90,7 @@ func configWriter(f *tview.Form){ var fp string var hostFile string var cfg []byte - var c database.HostDetails + var c model.HostDetails var hd string hd = viper.GetString("hostsDirectory") @@ -120,7 +120,7 @@ func configWriter(f *tview.Form){ } file, err := os.Create(fp) - c = database.HostDetails{ip[3], hostFile, ip[1], ip[2]} + c = model.HostDetails{ip[3], hostFile, ip[1], ip[2]} if err != nil { log.Fatal("unable to create file: " + fp) } diff --git a/cmd/load.go b/cmd/load.go index 31a7e73..8bf969d 100644 --- a/cmd/load.go +++ b/cmd/load.go @@ -26,8 +26,9 @@ import ( "io/ioutil" "log" "os" - "shiny-pancake/database" + "shiny-pancake/internal/database" "shiny-pancake/internal/gui" + "shiny-pancake/internal/model" "shiny-pancake/logger" "strconv" "strings" @@ -83,9 +84,10 @@ to quickly create a Cobra application.`, // Print out the final choice. if fn = <-result; len(fn) !=0 { var hostDir string + var db *database.Database hostDir = viper.GetString("hostsDirectory") file, _ := ioutil.ReadFile(hostDir + "/"+ fn) - data := database.HostDetails{} + data := model.HostDetails{} err := json.Unmarshal(file, &data) if err != nil { var l logger.Log @@ -97,7 +99,13 @@ to quickly create a Cobra application.`, var l logger.Log l = logger.Log{"info", logrus.Fields{"loaded": fn }, "config loading to ui"} logger.Lgr(&l) - gui.Gui(data) + db = database.New(&data) + + // Initialise the Gui / Tui + gui := gui.New(db) + if err := gui.Start(); err != nil { + log.Fatalf("main: Cannot start tui: %s", err) + } } }, diff --git a/cmd/root.go b/cmd/root.go index 09317b9..ea27726 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,7 @@ var ( hostsDirectory = "hosts" logsDirectory = "logs" configDirectory = "config" + queryDirectory = "query" ) var cfgFile string @@ -91,6 +92,7 @@ func initConfig() { viper.SetDefault("hostsDirectory", home + "/" + configHome + "/" + hostsDirectory) viper.SetDefault("logsDirectory", home + "/" + configHome + "/" + logsDirectory) viper.SetDefault("configDirectory", home + "/" +configHome + "/" + configDirectory) + viper.SetDefault("queryDirectory", home + "/" +configHome + "/" + queryDirectory) viper.SetDefault("logLevel", "debug") viper.SetDefault("logfile", "log") viper.AutomaticEnv() // read in environment variables that match @@ -118,6 +120,7 @@ func directoryInit() { logsDirectory, hostsDirectory, configDirectory, + queryDirectory, } for _, dr := range s { if _, err := os.Stat(cfgDir + "/" + dr); os.IsNotExist(err) { diff --git a/database/database.go b/database/database.go deleted file mode 100644 index 17fedf6..0000000 --- a/database/database.go +++ /dev/null @@ -1,68 +0,0 @@ -package database - -import ( - _ "github.com/lib/pq" -) - -//func DbConn(c HostDetails) (*sql.DB, error) { -// var h string -// var u string -// var pw string -// var dbn string -// var psqlInfo string -// h = c.Hostname -// u = c.Username -// pw = c.Secret -// dbn = c.DatabaseName -// -// logger.Logger("[LOG] connection details: host = " + h + " username = " + u) -// -// found, testUserName := data.ViperPgmConfig("test_user", "r") -// if found == true { -// logger.Logger("[LOG] found test user in .pgm.yaml") -// } -// psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+ -// "password=%s dbname=%s sslmode=disable", -// h, 5432, u, pw, dbn) -// db, err := sql.Open("postgres", psqlInfo) -// if err != nil { -// var db *sql.DB -// switch u { -// case testUserName: -// logger.Logger("[ERROR] go tests expects to connect with the docker compose database." + -// "[ERROR] run \"docker-compose up\" and re-run tests") -// err := errors.New("unable to ping db" + dbn + "local db is closed, please run docker-compose up") -// return db, err -// default: -// logger.Logger("[ERROR] db error: " + err.Error()) -// return db, err -// } -// -// } -// logger.Logger("[LOG] pinging db " + dbn) -// ctx := context.Background() -// err = db.PingContext(ctx) -// -// if err != nil { -// var db *sql.DB -// switch u { -// case testUserName: -// logger.Logger("[ERROR] db error, unable to ping: " + err.Error()+ -// "\n[ERROR] ensure docker database is running and re-run tests") -// fmt.Println("local db is closed, please run docker-compose up") -// err1 := errors.New("unable to ping db" + dbn) -// return db, err1 -// default: -// logger.Logger("[ERROR] db error: " + err.Error()) -// return db, err -// } -// } -// return db, nil -//} - -type HostDetails struct { - Secret string `json:"secret"` - Hostname string `json:"hostname"` - DatabaseName string `json:"databaseName"` - Username string `json:"username"` -} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index e69de29..cc212fd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: '3.7' + +services: + pg-db: + image: postgres:11.5 + container_name: pg-db + expose: + - "5432" + ports: + - "5432:5432" + environment: + - POSTGRES_DB=localdb + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + volumes: + - ${PWD}/internal/database/container/init.sql:/docker-entrypoint-initdb.d/init.sql + + diff --git a/go.mod b/go.mod index 4492d73..7617250 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.15 require ( github.com/charmbracelet/bubbletea v0.12.2 - github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 + github.com/gdamore/tcell v1.2.0 + github.com/gdamore/tcell/v2 v2.0.1-0.20201019142633-1057d5591ed1 + github.com/idlephysicist/cave-logger v1.2.2 github.com/lib/pq v1.8.0 github.com/mitchellh/go-homedir v1.1.0 github.com/muesli/termenv v0.7.4 @@ -12,4 +14,5 @@ require ( github.com/sirupsen/logrus v1.7.0 github.com/spf13/cobra v1.1.1 github.com/spf13/viper v1.7.1 + gitlab.com/tslocum/cview v1.5.1 ) diff --git a/go.sum b/go.sum index 11251cc..ace5c65 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,7 @@ cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiy dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -23,6 +24,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bvinc/go-sqlite-lite v0.6.1 h1:JU8Rz5YAOZQiU3WEulKF084wfXpytRiqD2IaW2QjPz4= +github.com/bvinc/go-sqlite-lite v0.6.1/go.mod h1:2GiE60NUdb0aNhDdY+LXgrqAVDpi2Ijc6dB6ZMp9x6s= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/charmbracelet/bubbletea v0.12.2 h1:y9Yo2Pv8tcm3mAJsWONGsmHhzrbNxJVxpVtemikxE9A= github.com/charmbracelet/bubbletea v0.12.2/go.mod h1:3gZkYELUOiEUOp0bTInkxguucy/xRbGSOcbMs1geLxg= @@ -43,8 +46,14 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.1.2/go.mod h1:h3kq4HO9l2On+V9ed8w8ewqQEmGCSSHOgQ+2h8uzurE= +github.com/gdamore/tcell v1.2.0 h1:ikixzsxc8K8o3V2/CEmyoEW8mJZaNYQQ3NP3VIQdUe4= +github.com/gdamore/tcell v1.2.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM= +github.com/gdamore/tcell/v2 v2.0.0-dev/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591 h1:0WWUDZ1oxq7NxVyGo8M3KI5jbkiwNAdZFFzAdC68up4= github.com/gdamore/tcell/v2 v2.0.1-0.20201017141208-acf90d56d591/go.mod h1:vSVL/GV5mCSlPC6thFP5kfOFdM9MGZcalipmpTxTgQA= +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/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 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= @@ -97,6 +106,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/idlephysicist/cave-logger v1.2.2 h1:PoGzbYx9rJ1l/hZDA3bDJUW9M++fTMAlAbC7AW8thbI= +github.com/idlephysicist/cave-logger v1.2.2/go.mod h1:BZG2qItPt8BnN/+T837nzY57qRihQXcb2YXUZoRYcrU= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -106,11 +117,13 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -118,6 +131,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.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +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.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= @@ -154,8 +168,10 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/tview v0.0.0-20190829161255-f8bc69b90341/go.mod h1:+rKjP5+h9HMwWRpAfhIkkQ9KE3m3Nz5rwn7YtUpwgqk= github.com/rivo/tview v0.0.0-20201018122409-d551c850a743 h1:9BBjVJTRxuYBeCAv9DFH2hSzY0ujLx5sxMg5D3K/Xeg= github.com/rivo/tview v0.0.0-20201018122409-d551c850a743/go.mod h1:t7mcA3nlK9dxD1DMoz/DQRMWFMkGBUj6rJBM5VNfLFA= +github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -165,6 +181,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -184,9 +201,14 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +gitlab.com/tslocum/cbind v0.1.3 h1:FT/fTQ4Yj3eo5021lB3IbkIt8eVtYGhrw/xur+cjvUU= +gitlab.com/tslocum/cbind v0.1.3/go.mod h1:RvwYE3auSjBNlCmWeGspzn+jdLUVQ8C2QGC+0nP9ChI= +gitlab.com/tslocum/cview v1.5.1 h1:1zHvNJvyhHxAVBf3FsSzzqgB15mrojNEm7VbvLXWcpQ= +gitlab.com/tslocum/cview v1.5.1/go.mod h1:BRtUi0zXzVXufhqFm/1GD7GL+iznKh5m9pEGN19SnKA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -249,24 +271,31 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190913121621-c3b328c6e5a7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201013132646-2da7054afaeb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7 h1:XtNJkfEjb4zR3q20BBBcYUykVOEMgZeIUOpBPfNYgxg= golang.org/x/sys v0.0.0-20201017003518-b09fb700fbb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1 h1:a/mKvvZr9Jcc8oKfcmgzyp7OwF73JPWsQLvH1z2Kxck= +golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -311,11 +340,13 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/database/container/init.sql b/internal/database/container/init.sql new file mode 100644 index 0000000..b7ff820 --- /dev/null +++ b/internal/database/container/init.sql @@ -0,0 +1,21 @@ +create schema test; +create table test.employees ( + Id SERIAL NOT NULL PRIMARY KEY, + Name VARCHAR(50), + Location VARCHAR(50) +); +create table test.company ( + cid SERIAL NOT NULL PRIMARY KEY, + Name VARCHAR(50) +); +create table test.employer ( + eid SERIAL NOT NULL PRIMARY KEY, + Id INT, + cid INT +); +ALTER TABLE test.employer add constraint emp_fk foreign key(Id) REFERENCES test.employees(id) ON UPDATE CASCADE ON DELETE CASCADE; +insert into test.employees (name,location) VALUES ('bob','kalamazoo'); +create role test with login password 'test'; +insert into test.employees (name,location) VALUES ('cheeks','magoo'); +update test.employees set location = 'buttsville' where name = 'cheeks'; +grant all privileges on schema test to test; diff --git a/internal/database/database.go b/internal/database/database.go new file mode 100644 index 0000000..ae68305 --- /dev/null +++ b/internal/database/database.go @@ -0,0 +1,77 @@ +package database + +import ( + "context" + "database/sql" + "fmt" + _ "github.com/lib/pq" + "github.com/sirupsen/logrus" + "shiny-pancake/internal/model" +) + +const datetime = `2006-01-02T15:04:05Z` +const date = `2006-01-02` + +type Database struct { + conn *sql.DB + log *logrus.Logger + ctx context.Context +} + +func New(c *model.HostDetails) *Database { + var db Database + var h string + var u string + var pw string + var dbn string + var psqlInfo string + var log *logrus.Logger + h = c.Hostname + u = c.Username + pw = c.Secret + dbn = c.DatabaseName + + db.ctx = context.Background() + db.log = log + psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+ + "password=%s dbname=%s sslmode=disable", + h, 5432, u, pw, dbn) + dbC, err := sql.Open("postgres", psqlInfo) + if err != nil { + log.Fatalf("database.new: Cannot establish connection to %s: %v", h, err) + } + db.conn = dbC + return &db +} + + +func (db *Database) UserSessions() ([]*model.Session, error) { + query := `select usename, count(usename) from pg_stat_activity where usename is not null group by usename` + + rows, err := db.conn.Query(query) + if err != nil { + // handle this error better than this + panic(err) + } + sessions := make([]*model.Session, 0) + defer rows.Close() + for rows.Next() { + var sessCount model.Session + err = rows.Scan(&sessCount.Username, &sessCount.Count) + + if err != nil { + // handle this error + panic(err) + } + sessions = append(sessions, &sessCount) + + } + // get any error encountered during iteration + err = rows.Err() + if err != nil { + panic(err) + } + + + return sessions, err +} \ No newline at end of file diff --git a/internal/database/query.toml b/internal/database/query.toml new file mode 100644 index 0000000..65fc79d --- /dev/null +++ b/internal/database/query.toml @@ -0,0 +1,4 @@ +[sessions.Current] +columns = 2 +headers = ["users", "count users"] +query = "select usename, count(usename) from pg_stat_activity where usename is not null" \ No newline at end of file diff --git a/internal/gui/data.go b/internal/gui/data.go index 0256e2a..c29fc78 100644 --- a/internal/gui/data.go +++ b/internal/gui/data.go @@ -1,367 +1,367 @@ package gui - -import ( - "database/sql" - "fmt" - "github.com/sirupsen/logrus" - "reflect" - "shiny-pancake/database" - "shiny-pancake/logger" - "strconv" - "sync" - "time" - - "github.com/gdamore/tcell/v2" - _ "github.com/lib/pq" - "github.com/rivo/tview" -) - -const ( - batchSize = 80 // The number of rows loaded per batch. - finderPage = "*finder*" // The name of the Finder page. -) - -var ( - app *tview.Application // The tview application. - pages *tview.Pages // The application pages. - finderFocus tview.Primitive // The primitive in the Finder that last had focus. -) - -// Main entry point. -func data(host database.HostDetails) { - var h string - var u string - var pw string - var dbn string - var psqlInfo string - h = host.Hostname - u = host.Username - pw = host.Secret - dbn = host.DatabaseName - // Start the application. - psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+ - "password=%s dbname=%s sslmode=disable", - h, 5432, u, pw, dbn) - app = tview.NewApplication() - finder(psqlInfo) - if err := app.Run(); err != nil { - fmt.Printf("Error running application: %s\n", err) - } -} - -// Sets up a "Finder" used to navigate the databases, tables, and columns. -func finder(connString string) { - // Create the basic objects. - databases := tview.NewList().ShowSecondaryText(false) - databases.SetBorder(true).SetTitle("Databases") - columns := tview.NewTable().SetBorders(true) - columns.SetBorder(true).SetTitle("Columns") - tables := tview.NewList() - tables.ShowSecondaryText(false). - SetDoneFunc(func() { - tables.Clear() - columns.Clear() - app.SetFocus(databases) - }) - tables.SetBorder(true).SetTitle("Tables") - - // Create the layout. - flex := tview.NewFlex(). - AddItem(databases, 0, 1, true). - AddItem(tables, 0, 1, false). - AddItem(columns, 0, 3, false) - - // We keep one connection pool per database. - dbMutex := sync.Mutex{} - dbs := make(map[string]*sql.DB) - getDB := func(database string) *sql.DB { - // Connect to a new database. - dbMutex.Lock() - defer dbMutex.Unlock() - if db, ok := dbs[database]; ok { - return db - } - db, err := sql.Open("postgres", connString) - if err != nil { - panic(err) - } - dbs[database] = db - return db - } - - // Get a list of all databases. - generalDB, err := sql.Open("postgres", connString) - if err != nil { - panic(err) - } - defer generalDB.Close() // We really close the DB because we only use it for this one query. - rows, err := generalDB.Query("select datname from pg_database where datistemplate = false") - if err != nil { - panic(err) - } - defer rows.Close() - for rows.Next() { - var dbName string - if err := rows.Scan(&dbName); err != nil { - panic(err) - } - databases.AddItem(dbName, "", 0, func() { - // A database was selected. Show all of its tables. - columns.Clear() - tables.Clear() - db := getDB(dbName) - t, err := db.Query("select table_name from information_schema.tables where table_schema not in ('information_schema','pg_catalog')") - if err != nil { - panic(err) - } - defer t.Close() - for t.Next() { - var tableName string - if err := t.Scan(&tableName); err != nil { - panic(err) - } - tables.AddItem(tableName, "", 0, nil) - } - if err := t.Err(); err != nil { - panic(err) - } - app.SetFocus(tables) - - // When the user navigates to a table, show its columns. - tables.SetChangedFunc(func(i int, tableName string, t string, s rune) { - // A table was selected. Show its columns. - columns.Clear() - c, err := db.Query(` - select - c.column_name, - c.is_nullable, - c.data_type, - c.character_maximum_length, - c.numeric_precision, - c.numeric_scale, - c.ordinal_position, - tc.constraint_type pkey - from - information_schema.columns c - left join - information_schema.constraint_column_usage as ccu - on - c.table_schema = ccu.table_schema - and c.table_name = ccu.table_name - and c.column_name = ccu.column_name - left join - information_schema.table_constraints as tc - on - ccu.constraint_schema = tc.constraint_schema - and ccu.constraint_name = tc.constraint_name - where - c.table_schema not in ('information_schema','pg_catalog') - and c.table_name = $1 - `, tableName) - if err != nil { - var l logger.Log - l = logger.Log{"error", logrus.Fields{"database": err.Error() }, "an error occured in view."} - logger.Lgr(&l) - } - defer c.Close() - columns.SetCell(0, 0, &tview.TableCell{Text: "Name", Align: tview.AlignCenter, Color: tcell.ColorYellow}). - SetCell(0, 1, &tview.TableCell{Text: "Type", Align: tview.AlignCenter, Color: tcell.ColorYellow}). - SetCell(0, 2, &tview.TableCell{Text: "Size", Align: tview.AlignCenter, Color: tcell.ColorYellow}). - SetCell(0, 3, &tview.TableCell{Text: "Null", Align: tview.AlignCenter, Color: tcell.ColorYellow}). - SetCell(0, 4, &tview.TableCell{Text: "Constraint", Align: tview.AlignCenter, Color: tcell.ColorYellow}) - for c.Next() { - var ( - columnName, isNullable, dataType string - constraintType sql.NullString - size, numericPrecision, numericScale sql.NullInt64 - ordinalPosition int - ) - if err := c.Scan(&columnName, - &isNullable, - &dataType, - &size, - &numericPrecision, - &numericScale, - &ordinalPosition, - &constraintType, - ); err != nil { - panic(err) - } - sizeText := "" - if size.Valid { - sizeText = strconv.Itoa(int(size.Int64)) - } else if numericPrecision.Valid { - sizeText = strconv.Itoa(int(numericPrecision.Int64)) - if numericScale.Valid { - sizeText += "," + strconv.Itoa(int(numericScale.Int64)) - } - } - color := tcell.ColorWhite - if constraintType.Valid { - color = map[string]tcell.Color{ - "CHECK": tcell.ColorGreen, - "FOREIGN KEY": tcell.ColorDarkMagenta, - "PRIMARY KEY": tcell.ColorRed, - "UNIQUE": tcell.ColorDarkCyan, - }[constraintType.String] - } - columns.SetCell(ordinalPosition, 0, &tview.TableCell{Text: columnName, Color: color}). - SetCell(ordinalPosition, 1, &tview.TableCell{Text: dataType, Color: color}). - SetCell(ordinalPosition, 2, &tview.TableCell{Text: sizeText, Align: tview.AlignRight, Color: color}). - SetCell(ordinalPosition, 3, &tview.TableCell{Text: isNullable, Align: tview.AlignRight, Color: color}). - SetCell(ordinalPosition, 4, &tview.TableCell{Text: constraintType.String, Align: tview.AlignLeft, Color: color}) - } - if err := c.Err(); err != nil { - panic(err) - } - }) - tables.SetCurrentItem(0) // Trigger the initial selection. - - // When the user selects a table, show its content. - tables.SetSelectedFunc(func(i int, tableName string, t string, s rune) { - content(db, dbName, tableName) - }) - }) - } - if err := rows.Err(); err != nil { - panic(err) - } - - // Set up the pages and show the Finder. - pages = tview.NewPages(). - AddPage(finderPage, flex, true, true) - app.SetRoot(pages, true) -} - -// Shows the contents of the given table. -func content(db *sql.DB, dbName, tableName string) { - finderFocus = app.GetFocus() - - // If this page already exists, just show it. - if pages.HasPage(dbName + "." + tableName) { - pages.SwitchToPage(dbName + "." + tableName) - return - } - var l logger.Log - l = logger.Log{"info", logrus.Fields{"database": tableName }, "selected table to view"} - logger.Lgr(&l) - var schemaName string - err := db.QueryRow(fmt.Sprintf("select table_schema from information_schema.tables where table_schema not in ('information_schema','pg_catalog') and table_name = '%s'", tableName)).Scan(&schemaName) - if err != nil { - panic(err) - } - - // We display the data in a table embedded in a frame. - table := tview.NewTable(). - SetFixed(1, 0). - SetBordersColor(tcell.ColorYellow) - frame := tview.NewFrame(table). - SetBorders(0, 0, 0, 0, 0, 0) - frame.SetBorder(true). - SetTitle(fmt.Sprintf(`Contents of table "%s.%s"`,schemaName, tableName)) - - // How many rows does this table have? - var rowCount int - err = db.QueryRow(fmt.Sprintf("select count(*) from %s.%s", schemaName, tableName)).Scan(&rowCount) - if err != nil { - panic(err) - } - - // Load a batch of rows. - loadRows := func(offset int) { - rows, err := db.Query(fmt.Sprintf("select * from %s.%s limit $1 offset $2", schemaName, tableName), batchSize, offset) - if err != nil { - panic(err) - } - defer rows.Close() - - // The first row in the table is the list of column names. - columnNames, err := rows.Columns() - if err != nil { - panic(err) - } - for index, name := range columnNames { - table.SetCell(0, index, &tview.TableCell{Text: name, Align: tview.AlignCenter, Color: tcell.ColorYellow}) - } - - // Read the rows. - columns := make([]interface{}, len(columnNames)) - columnPointers := make([]interface{}, len(columns)) - for index := range columnPointers { - columnPointers[index] = &columns[index] - } - for rows.Next() { - // Read the columns. - err := rows.Scan(columnPointers...) - if err != nil { - panic(err) - } - - // Transfer them to the table. - row := table.GetRowCount() - for index, column := range columns { - switch value := column.(type) { - case int64: - table.SetCell(row, index, &tview.TableCell{Text: strconv.Itoa(int(value)), Align: tview.AlignRight, Color: tcell.ColorDarkCyan}) - case float64: - table.SetCell(row, index, &tview.TableCell{Text: strconv.FormatFloat(value, 'f', 2, 64), Align: tview.AlignRight, Color: tcell.ColorDarkCyan}) - case string: - table.SetCellSimple(row, index, value) - case time.Time: - t := value.Format("2006-01-02") - table.SetCell(row, index, &tview.TableCell{Text: t, Align: tview.AlignRight, Color: tcell.ColorDarkMagenta}) - case []uint8: - str := make([]byte, len(value)) - for index, num := range value { - str[index] = byte(num) - } - table.SetCell(row, index, &tview.TableCell{Text: string(str), Align: tview.AlignRight, Color: tcell.ColorGreen}) - case nil: - table.SetCell(row, index, &tview.TableCell{Text: "NULL", Align: tview.AlignCenter, Color: tcell.ColorRed}) - default: - // We've encountered a type that we don't know yet. - t := reflect.TypeOf(value) - str := "?nil?" - if t != nil { - str = "?" + t.String() + "?" - } - table.SetCellSimple(row, index, str) - } - } - } - if err := rows.Err(); err != nil { - panic(err) - } - - // Show how much we've loaded. - frame.Clear() - loadMore := "" - if table.GetRowCount()-1 < rowCount { - loadMore = " - press Enter to load more" - } - loadMore = fmt.Sprintf("Loaded %d of %d rows%s. esc to return to previous menu", table.GetRowCount()-1, rowCount, loadMore) - frame.AddText(loadMore, false, tview.AlignCenter, tcell.ColorYellow) - } - - // Load the first batch of rows. - loadRows(0) - - // Handle key presses. - table.SetDoneFunc(func(key tcell.Key) { - switch key { - case tcell.KeyEscape: - // Go back to Finder. - pages.SwitchToPage(finderPage) - if finderFocus != nil { - app.SetFocus(finderFocus) - } - case tcell.KeyEnter: - // Load the next batch of rows. - loadRows(table.GetRowCount() - 1) - table.ScrollToEnd() - } - }) - - // Add a new page and show it. - pages.AddPage(dbName+"."+tableName, frame, true, true) -} +// +//import ( +// "database/sql" +// "fmt" +// "github.com/sirupsen/logrus" +// "reflect" +// "shiny-pancake/internal/database" +// "shiny-pancake/logger" +// "strconv" +// "sync" +// "time" +// +// "github.com/gdamore/tcell/v2" +// _ "github.com/lib/pq" +// "github.com/rivo/tview" +//) +// +//const ( +// batchSize = 80 // The number of rows loaded per batch. +// finderPage = "*finder*" // The name of the Finder page. +//) +// +//var ( +// app *tview.Application // The tview application. +// panels *tview.Pages // The application panels. +// finderFocus tview.Primitive // The primitive in the Finder that last had focus. +//) +// +//// Main entry point. +//func data(host database.HostDetails) { +// var h string +// var u string +// var pw string +// var dbn string +// var psqlInfo string +// h = host.Hostname +// u = host.Username +// pw = host.Secret +// dbn = host.DatabaseName +// // Start the application. +// psqlInfo = fmt.Sprintf("host=%s port=%d user=%s "+ +// "password=%s dbname=%s sslmode=disable", +// h, 5432, u, pw, dbn) +// app = tview.NewApplication() +// finder(psqlInfo) +// if err := app.Run(); err != nil { +// fmt.Printf("Error running application: %s\n", err) +// } +//} +// +//// Sets up a "Finder" used to navigate the databases, tables, and columns. +//func finder(connString string) { +// // Create the basic objects. +// databases := tview.NewList().ShowSecondaryText(false) +// databases.SetBorder(true).SetTitle("Databases") +// columns := tview.NewTable().SetBorders(true) +// columns.SetBorder(true).SetTitle("Columns") +// tables := tview.NewList() +// tables.ShowSecondaryText(false). +// SetDoneFunc(func() { +// tables.Clear() +// columns.Clear() +// app.SetFocus(databases) +// }) +// tables.SetBorder(true).SetTitle("Tables") +// +// // Create the layout. +// flex := tview.NewFlex(). +// AddItem(databases, 0, 1, true). +// AddItem(tables, 0, 1, false). +// AddItem(columns, 0, 3, false) +// +// // We keep one connection pool per database. +// dbMutex := sync.Mutex{} +// dbs := make(map[string]*sql.DB) +// getDB := func(database string) *sql.DB { +// // Connect to a new database. +// dbMutex.Lock() +// defer dbMutex.Unlock() +// if db, ok := dbs[database]; ok { +// return db +// } +// db, err := sql.Open("postgres", connString) +// if err != nil { +// panic(err) +// } +// dbs[database] = db +// return db +// } +// +// // Get a list of all databases. +// generalDB, err := sql.Open("postgres", connString) +// if err != nil { +// panic(err) +// } +// defer generalDB.Close() // We really close the DB because we only use it for this one query. +// rows, err := generalDB.Query("select datname from pg_database where datistemplate = false") +// if err != nil { +// panic(err) +// } +// defer rows.Close() +// for rows.Next() { +// var dbName string +// if err := rows.Scan(&dbName); err != nil { +// panic(err) +// } +// databases.AddItem(dbName, "", 0, func() { +// // A database was selected. Show all of its tables. +// columns.Clear() +// tables.Clear() +// db := getDB(dbName) +// t, err := db.Query("select table_name from information_schema.tables where table_schema not in ('information_schema','pg_catalog')") +// if err != nil { +// panic(err) +// } +// defer t.Close() +// for t.Next() { +// var tableName string +// if err := t.Scan(&tableName); err != nil { +// panic(err) +// } +// tables.AddItem(tableName, "", 0, nil) +// } +// if err := t.Err(); err != nil { +// panic(err) +// } +// app.SetFocus(tables) +// +// // When the user navigates to a table, show its columns. +// tables.SetChangedFunc(func(i int, tableName string, t string, s rune) { +// // A table was selected. Show its columns. +// columns.Clear() +// c, err := db.Query(` +// select +// c.column_name, +// c.is_nullable, +// c.data_type, +// c.character_maximum_length, +// c.numeric_precision, +// c.numeric_scale, +// c.ordinal_position, +// tc.constraint_type pkey +// from +// information_schema.columns c +// left join +// information_schema.constraint_column_usage as ccu +// on +// c.table_schema = ccu.table_schema +// and c.table_name = ccu.table_name +// and c.column_name = ccu.column_name +// left join +// information_schema.table_constraints as tc +// on +// ccu.constraint_schema = tc.constraint_schema +// and ccu.constraint_name = tc.constraint_name +// where +// c.table_schema not in ('information_schema','pg_catalog') +// and c.table_name = $1 +// `, tableName) +// if err != nil { +// var l logger.Log +// l = logger.Log{"error", logrus.Fields{"database": err.Error()}, "an error occured in view."} +// logger.Lgr(&l) +// } +// defer c.Close() +// columns.SetCell(0, 0, &tview.TableCell{Text: "Name", Align: tview.AlignCenter, Color: tcell.ColorYellow}). +// SetCell(0, 1, &tview.TableCell{Text: "Type", Align: tview.AlignCenter, Color: tcell.ColorYellow}). +// SetCell(0, 2, &tview.TableCell{Text: "Size", Align: tview.AlignCenter, Color: tcell.ColorYellow}). +// SetCell(0, 3, &tview.TableCell{Text: "Null", Align: tview.AlignCenter, Color: tcell.ColorYellow}). +// SetCell(0, 4, &tview.TableCell{Text: "Constraint", Align: tview.AlignCenter, Color: tcell.ColorYellow}) +// for c.Next() { +// var ( +// columnName, isNullable, dataType string +// constraintType sql.NullString +// size, numericPrecision, numericScale sql.NullInt64 +// ordinalPosition int +// ) +// if err := c.Scan(&columnName, +// &isNullable, +// &dataType, +// &size, +// &numericPrecision, +// &numericScale, +// &ordinalPosition, +// &constraintType, +// ); err != nil { +// panic(err) +// } +// sizeText := "" +// if size.Valid { +// sizeText = strconv.Itoa(int(size.Int64)) +// } else if numericPrecision.Valid { +// sizeText = strconv.Itoa(int(numericPrecision.Int64)) +// if numericScale.Valid { +// sizeText += "," + strconv.Itoa(int(numericScale.Int64)) +// } +// } +// color := tcell.ColorWhite +// if constraintType.Valid { +// color = map[string]tcell.Color{ +// "CHECK": tcell.ColorGreen, +// "FOREIGN KEY": tcell.ColorDarkMagenta, +// "PRIMARY KEY": tcell.ColorRed, +// "UNIQUE": tcell.ColorDarkCyan, +// }[constraintType.String] +// } +// columns.SetCell(ordinalPosition, 0, &tview.TableCell{Text: columnName, Color: color}). +// SetCell(ordinalPosition, 1, &tview.TableCell{Text: dataType, Color: color}). +// SetCell(ordinalPosition, 2, &tview.TableCell{Text: sizeText, Align: tview.AlignRight, Color: color}). +// SetCell(ordinalPosition, 3, &tview.TableCell{Text: isNullable, Align: tview.AlignRight, Color: color}). +// SetCell(ordinalPosition, 4, &tview.TableCell{Text: constraintType.String, Align: tview.AlignLeft, Color: color}) +// } +// if err := c.Err(); err != nil { +// panic(err) +// } +// }) +// tables.SetCurrentItem(0) // Trigger the initial selection. +// +// // When the user selects a table, show its content. +// tables.SetSelectedFunc(func(i int, tableName string, t string, s rune) { +// content(db, dbName, tableName) +// }) +// }) +// } +// if err := rows.Err(); err != nil { +// panic(err) +// } +// +// // Set up the panels and show the Finder. +// panels = tview.NewPages(). +// AddPage(finderPage, flex, true, true) +// app.SetRoot(panels, true) +//} +// +//// Shows the contents of the given table. +//func content(db *sql.DB, dbName, tableName string) { +// finderFocus = app.GetFocus() +// +// // If this page already exists, just show it. +// if panels.HasPage(dbName + "." + tableName) { +// panels.SwitchToPage(dbName + "." + tableName) +// return +// } +// var l logger.Log +// l = logger.Log{"info", logrus.Fields{"database": tableName}, "selected table to view"} +// logger.Lgr(&l) +// var schemaName string +// err := db.QueryRow(fmt.Sprintf("select table_schema from information_schema.tables where table_schema not in ('information_schema','pg_catalog') and table_name = '%s'", tableName)).Scan(&schemaName) +// if err != nil { +// panic(err) +// } +// +// // We display the data in a table embedded in a frame. +// table := tview.NewTable(). +// SetFixed(1, 0). +// SetBordersColor(tcell.ColorYellow) +// frame := tview.NewFrame(table). +// SetBorders(0, 0, 0, 0, 0, 0) +// frame.SetBorder(true). +// SetTitle(fmt.Sprintf(`Contents of table "%s.%s"`, schemaName, tableName)) +// +// // How many rows does this table have? +// var rowCount int +// err = db.QueryRow(fmt.Sprintf("select count(*) from %s.%s", schemaName, tableName)).Scan(&rowCount) +// if err != nil { +// panic(err) +// } +// +// // Load a batch of rows. +// loadRows := func(offset int) { +// rows, err := db.Query(fmt.Sprintf("select * from %s.%s limit $1 offset $2", schemaName, tableName), batchSize, offset) +// if err != nil { +// panic(err) +// } +// defer rows.Close() +// +// // The first row in the table is the list of column names. +// columnNames, err := rows.Columns() +// if err != nil { +// panic(err) +// } +// for index, name := range columnNames { +// table.SetCell(0, index, &tview.TableCell{Text: name, Align: tview.AlignCenter, Color: tcell.ColorYellow}) +// } +// +// // Read the rows. +// columns := make([]interface{}, len(columnNames)) +// columnPointers := make([]interface{}, len(columns)) +// for index := range columnPointers { +// columnPointers[index] = &columns[index] +// } +// for rows.Next() { +// // Read the columns. +// err := rows.Scan(columnPointers...) +// if err != nil { +// panic(err) +// } +// +// // Transfer them to the table. +// row := table.GetRowCount() +// for index, column := range columns { +// switch value := column.(type) { +// case int64: +// table.SetCell(row, index, &tview.TableCell{Text: strconv.Itoa(int(value)), Align: tview.AlignRight, Color: tcell.ColorDarkCyan}) +// case float64: +// table.SetCell(row, index, &tview.TableCell{Text: strconv.FormatFloat(value, 'f', 2, 64), Align: tview.AlignRight, Color: tcell.ColorDarkCyan}) +// case string: +// table.SetCellSimple(row, index, value) +// case time.Time: +// t := value.Format("2006-01-02") +// table.SetCell(row, index, &tview.TableCell{Text: t, Align: tview.AlignRight, Color: tcell.ColorDarkMagenta}) +// case []uint8: +// str := make([]byte, len(value)) +// for index, num := range value { +// str[index] = byte(num) +// } +// table.SetCell(row, index, &tview.TableCell{Text: string(str), Align: tview.AlignRight, Color: tcell.ColorGreen}) +// case nil: +// table.SetCell(row, index, &tview.TableCell{Text: "NULL", Align: tview.AlignCenter, Color: tcell.ColorRed}) +// default: +// // We've encountered a type that we don't know yet. +// t := reflect.TypeOf(value) +// str := "?nil?" +// if t != nil { +// str = "?" + t.String() + "?" +// } +// table.SetCellSimple(row, index, str) +// } +// } +// } +// if err := rows.Err(); err != nil { +// panic(err) +// } +// +// // Show how much we've loaded. +// frame.Clear() +// loadMore := "" +// if table.GetRowCount()-1 < rowCount { +// loadMore = " - press Enter to load more" +// } +// loadMore = fmt.Sprintf("Loaded %d of %d rows%s. esc to return to previous menu", table.GetRowCount()-1, rowCount, loadMore) +// frame.AddText(loadMore, false, tview.AlignCenter, tcell.ColorYellow) +// } +// +// // Load the first batch of rows. +// loadRows(0) +// +// // Handle key presses. +// table.SetDoneFunc(func(key tcell.Key) { +// switch key { +// case tcell.KeyEscape: +// // Go back to Finder. +// panels.SwitchToPage(finderPage) +// if finderFocus != nil { +// app.SetFocus(finderFocus) +// } +// case tcell.KeyEnter: +// // Load the next batch of rows. +// loadRows(table.GetRowCount() - 1) +// table.ScrollToEnd() +// } +// }) +// +// // Add a new page and show it. +// panels.AddPage(dbName+"."+tableName, frame, true, true) +//} diff --git a/internal/gui/gui.go b/internal/gui/gui.go index d419212..0a926c0 100644 --- a/internal/gui/gui.go +++ b/internal/gui/gui.go @@ -1,6 +1,15 @@ package gui -import "github.com/rivo/tview" +import ( + "fmt" + "github.com/sirupsen/logrus" + "shiny-pancake/logger" + + "shiny-pancake/internal/database" + "strings" + + tview "gitlab.com/tslocum/cview" +) type panels struct { currentPanel int @@ -23,8 +32,84 @@ func newState() *state { type Gui struct { app *tview.Application - pages *tview.Pages + panels *tview.Panels state *state - db *db.Database - //statsLocations *statsLocations -} \ No newline at end of file + db *database.Database +} + +func New(db *database.Database) *Gui { + return &Gui{ + app: tview.NewApplication(), + panels: tview.NewPanels(), + state: newState(), + db: db, + } +} + + +// Start start application +func (g *Gui) Start() error { + g.initPanels() + 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 + } + + return nil +} + +func (g *Gui) Stop() { + g.app.Stop() +} + +// Page "definitions" + +func (g *Gui) sessionPanel() *activeSessions { + for _, panel := range g.state.panels.panel { + if panel.name() == `activeSessions` { + return panel.(*activeSessions) + } + } + return nil +} + + +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) + + /* + // NOTE: I would really like to get this working as it would be far neater. + // The issue is with the three panels being of different types. + // cannot use pg (type panel) as type tview.Primitive in argument to g.panels.AddPage: + // panel does not implement tview.Primitive (missing Blur method) + for idx, pg := range []panel{trips, cavers, caves} { + name := pg.name() + g.panels.AddPage(name, pg, true, idx == 0) + fmt.Fprintf(g.state.tabBar, ` %d ["%d"][darkcyan]%s[white][""] `, idx+1, idx, strings.Title(name)) + } + g.state.tabBar.Highlight("0") + */ + + // Add panels to the "book" + g.panels.AddPanel(`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) + + + g.app.SetRoot(g.panels, true) +} diff --git a/internal/gui/monitoring.go b/internal/gui/monitoring.go new file mode 100644 index 0000000..2743dd7 --- /dev/null +++ b/internal/gui/monitoring.go @@ -0,0 +1,18 @@ +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 ? + }) +}*/ diff --git a/internal/gui/panel.go b/internal/gui/panel.go new file mode 100644 index 0000000..40ef44f --- /dev/null +++ b/internal/gui/panel.go @@ -0,0 +1,9 @@ +package gui + +type panel interface { + name() string + entries(*Gui) + //setEntries(*Gui) + //focus(*Gui) + //unfocus() +} diff --git a/internal/gui/sessions.go b/internal/gui/sessions.go new file mode 100644 index 0000000..5aa2511 --- /dev/null +++ b/internal/gui/sessions.go @@ -0,0 +1,46 @@ +package gui + +import ( + tview "gitlab.com/tslocum/cview" + "shiny-pancake/internal/model" +) + +type resources struct { + ActSess []*model.Session + //statsLocations []*model.Statistic +} + + +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 +} + diff --git a/internal/gui/tabbar.go b/internal/gui/tabbar.go new file mode 100644 index 0000000..368e112 --- /dev/null +++ b/internal/gui/tabbar.go @@ -0,0 +1,10 @@ +package gui + + +import ( +tview "gitlab.com/tslocum/cview" +) + +func newTabBar() *tview.TextView { + return tview.NewTextView() +} diff --git a/internal/model/forms.go b/internal/model/forms.go new file mode 100644 index 0000000..fb0943a --- /dev/null +++ b/internal/model/forms.go @@ -0,0 +1,13 @@ +package model + +type HostDetails struct { + Secret string `json:"secret"` + Hostname string `json:"hostname"` + DatabaseName string `json:"databaseName"` + Username string `json:"username"` +} + +type Session struct { + Username string + Count int +} diff --git a/main b/main index 3efd364..41e5daa 100755 Binary files a/main and b/main differ