pgm/loader/loader.go
2020-10-25 10:27:09 -05:00

290 lines
6.0 KiB
Go

package loader
import (
//"database/sql"
"encoding/json"
"fmt"
tea "github.com/charmbracelet/bubbletea"
//"github.com/mitchellh/mapstructure"
"github.com/muesli/termenv"
"github.com/spf13/viper"
"io/ioutil"
"os"
"pgm/config"
"pgm/data"
//"pgm/db"
"pgm/logger"
"strconv"
"strings"
"time"
)
type host struct {
tic int
t string
choices []string
choice int
chosen bool
Quitting bool
Frames int
File string
isTest bool
}
var (
term = termenv.ColorProfile()
subtle = makeFgStyle("241")
dot = colorFg(" • ", "236")
)
func Loader(t []string, s string, b string) error {
var hostChoice host
var isT bool
isT = setTest(s)
hostChoice = host{60, s, t, 0, false, false, 0, b, isT}
if err := tea.NewProgram(hostChoice).Start(); err != nil {
if isT == true {
return nil
}
logger.Logger("[ERROR] tea error occured")
os.Exit(1)
}
return nil
}
// method to enable go tests if set to true
// loader function will test that it can load the ui to chose a database
// and quits the ui
func setTest(s string) bool {
if s == "test" {
logger.Logger("[LOG] is test: true")
return true
}
logger.Logger("[LOG] is test: false")
return false
}
func (m host) Init() tea.Cmd {
return tick()
}
func choicesUpdate(msg tea.Msg, m host) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "q", "esc":
m.Quitting = true
return m, tea.Quit
case "j", "down":
m.choice += 1
if m.choice > len(m.choices) {
m.choice = len(m.choices)
}
case "k", "up":
m.choice -= 1
if m.choice < 0 {
m.choice = 0
}
case "l", "enter":
m.File = m.choices[m.choice]
m.chosen = true
return m, Frame()
}
case tickMsg:
if m.isTest == true {
m.File = "test"
m.Quitting = true
return m, tea.Quit
}
if m.tic == 0 {
m.Quitting = true
return m, tea.Quit
}
m.tic -= 1
return m, tick()
}
return m, nil
}
// Update loop for the second view after a choice has been made
func (m host) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
if !m.chosen {
return choicesUpdate(msg, m)
}
return m, tea.Quit
}
// Views return a string based on data in the host. That string which will be
// rendered to the terminal.
func (m host) View() string {
var s string
if m.Quitting {
logger.Logger("[LOG] leaving pgm")
return "leaving pgm..."
}
if !m.chosen {
s = choicesView(m)
} else {
tea.Quit()
logger.Logger("[LOG] " + m.File + " chosen")
LoadUi(m)
}
return s
}
// Passes our config to the screen file.
// screen is tasked with rendering more in depth views.
func LoadUi(m host) {
var g data.HostDetails
var h string
h, err := os.UserHomeDir()
if err != nil {
logger.Logger("[ERROR] uable to set home dir: " + err.Error())
os.Exit(0)
}
config.ReadConfig()
l := viper.GetString("hosts_dir")
filepath := h + "/" + l + "/" + m.File
loadhost(filepath, &g)
Screener(g)
}
// Creates the menu for choosing a file from your configs.
// Exits after 60 seconds.
// Gets Title from outside in the load method if this... should ever be needed elsewhere.
func choicesView(m host) string {
var menu string
c := m.choice
tpl := m.t + "\n\n"
tpl += "%s\n\n"
tpl += "menu exits in %s seconds\n\n"
tpl += subtle("j/k, up/down: select") + dot + subtle("enter: choose") + dot + subtle("q, esc: quit")
for i, s := range m.choices {
menu = menu + "::" + checkbox(s, c == i)
}
mn := strings.Replace(menu, "::", "\n", -1)
choices := fmt.Sprintf(
mn,
)
return fmt.Sprintf(tpl, choices, colorFg(strconv.Itoa(m.tic), "79"))
}
// the rest of these functions are to do with looks and aesthetics
// returns a neat checkbox
func checkbox(label string, checked bool) string {
if checked {
return colorFg("[x] "+label, "212")
}
return fmt.Sprintf("[ ] %s", label)
}
// fun colors
func colorFg(val, color string) string {
return termenv.String(val).Foreground(term.Color(color)).String()
}
// Return a function that will colorize the foreground of a given string.
func makeFgStyle(color string) func(string) string {
return termenv.Style{}.Foreground(term.Color(color)).Styled
}
// Frame event
func Frame() tea.Cmd {
return tea.Tick(time.Second/60, func(time.Time) tea.Msg {
return FrameMsg{}
})
}
type tickMsg struct{}
type FrameMsg struct{}
// exits if no choice is selected in a minute
func tick() tea.Cmd {
return tea.Tick(time.Second, func(time.Time) tea.Msg {
return tickMsg{}
})
}
// loads a yaml file into the correct format for consumption.
// errs the pipeline step if there is not a configuration in this repository.
func loadhost(c string, p *data.HostDetails) *data.HostDetails {
cfg, err := ioutil.ReadFile(c)
if err != nil {
logger.Logger("[ERROR] unable to find this filepath: " + err.Error())
os.Exit(1)
}
json.Unmarshal(cfg, &p)
return p
}
func Screener(g data.HostDetails) {
//var scn Scr
//var dbConn *sql.DB
//var s interface{}
logger.Logger("[LOG] starting screen")
os.Exit(0)
//
//found, s := data.ViperScrConfig("userSessions", "r")
//mapstructure.Decode(s, &scn)
//logger.Logger("[LOG] scn value " + scn.Title)
//if found == true {
// logger.Logger("[LOG] found screen: " + scn.Title)
//} else {
// logger.Logger("[LOG] no screen found")
//}
//
//dbConn = db.DbConn(g)
//switch g.Username {
//case "test":
// dbConn.Close()
// return nil
//}
////ui.GraphicUi(dbConn)
//return nil
}
// Simple struct for a view in the UI.
type Scr struct {
Query string `json:"query"`
Columns int `json:"columns"`
UIType string `json:"uiType"`
Title string `json:"title"`
Quitting bool
}
func (m Scr) Init() tea.Cmd {
return tick()
}
func (m Scr) View() string {
return m.Query
}
func (m Scr) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if msg, ok := msg.(tea.KeyMsg); ok {
k := msg.String()
if k == "q" || k == "esc" || k == "ctrl+c" {
m.Quitting = true
return m, tea.Quit
}
}
return m, nil
}