You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

232 lines
6.5 KiB
Go

package tui
import (
"fmt"
"log"
"sync"
"strconv"
"strings"
"time"
"FRMS/internal/pkg/logging"
"github.com/rivo/tview"
_ "github.com/gdamore/tcell/v2"
"os"
)
type Device struct {
Id uint32
Type string
Status string
Data string
Index uint32
}
type TUI struct {
*Display
*TUIClient
SelectedReactor <-chan uint32
SelectedDevice <-chan uint32
Err chan error
}
func NewTUI(ip string, port int, ifconfig string, ch chan error) *TUI {
t := &TUI{}
t.Err = ch
client := NewTUIClient(ip, port, ifconfig)
t.TUIClient = client
return t
}
func (t *TUI) Start() {
// setup tview app and wait for user connection in standin modal
if err := t.TUIClient.Start(); err != nil {
t.Err <- err
}
logging.Debug(logging.DStart, "TUI %v starting", t.Id)
go t.Monitor()
t.CreateDisplay()
t.Display.Start()
}
func (t *TUI) CreateDisplay() {
rc := make(chan uint32)
dc := make(chan uint32)
t.Display = NewDisplay(rc,dc)
t.SelectedReactor = rc
t.SelectedDevice = dc
t.Flex.AddItem(t.ReactorList,0,1,true).
AddItem(t.DeviceList,0,2,false)
}
func (t *TUI) Monitor() {
// orchestrates updates and grpc requests
timer := make(chan struct{})
go func(signal chan struct{}){
for {
signal <- struct{}{}
time.Sleep(1 * time.Second)
}
}(timer)
for {
select {
case reactor := <-t.SelectedReactor:
// reactor has been selected in tui, grabbing devs
t.App.QueueUpdateDraw(func() {
t.UpdateDevices(reactor)
})
logging.Debug(logging.DClient, "%v getting reactor devices", t.Id)
case dev := <-t.SelectedDevice:
logging.Debug(logging.DClient, "%v editing device %v", t.Id, dev)
// TODO
case <-timer:
// time to ping for status
logging.Debug(logging.DClient, "%v pinging for updates", t.Id)
t.App.QueueUpdateDraw(func() {
t.UpdateDevices()
})
}
}
}
func (t *TUI) UpdateDevices(r ...uint32) {
// get devices for the reactor and update the tui
// see if there is a page being displayed
// overwrite if called as a func
var devs map[uint32]*Device
var err error
if len(r) > 0 {
// could be a reactor id or 1 for update reactors
if r[0] != 0 {
t.Display.DeviceList.Clear()
} else {
t.ReactorList.Clear()
t.ReactorList.AddItem("Refresh","Press (r) to refresh", 114, nil)
t.ReactorList.AddItem("Quit","Press (q) to quit",113,func() {
t.App.Stop()
os.Exit(0)
})
}
devs, err = t.TUIClient.GetDevices(r[0])
} else {
devs, err = t.TUIClient.GetDevices()
}
if err != nil {
log.Fatal(err)
}
//if id != 0 {
// split based on type to simplify update
reactors := make(map[uint32]*Device)
devices := make(map[uint32]*Device)
for id, dev := range devs {
if dev.Type == "Reactor" {
reactors[id] = dev
} else {
devices[id] = dev
}
}
t.DisplayDevices(devices)
t.DisplayReactors(reactors)
}
// display struct and logic
type Display struct {
App *tview.Application
Flex *tview.Flex
ReactorList *tview.List
DeviceList *tview.List
SelectedReactor chan<- uint32
SelectedDevice chan<- uint32
sync.Mutex
}
func NewDisplay(rc,dc chan uint32) *Display {
d := &Display{}
d.App = tview.NewApplication()
d.Flex = tview.NewFlex()
d.DeviceList = tview.NewList()
d.ReactorList = tview.NewList()
d.ReactorList.AddItem("Refresh","Press (r) to refresh manually", 114, nil)
d.ReactorList.AddItem("Quit","Press (q) to quit",113,func() {
d.App.Stop()
os.Exit(0)
})
d.ReactorList.SetTitle("Reactors").SetBorder(true)
d.ReactorList.SetSelectedFunc(d.SelectReactor)
d.DeviceList.SetTitle("Devices").SetBorder(true)
d.DeviceList.SetSelectedFunc(d.SelectDevice)
d.SelectedReactor = rc
d.SelectedDevice = dc
return d
}
func (d *Display) Start() {
if err := d.App.SetRoot(d.Flex, true).Run(); err != nil {
d.App.Stop()
log.Fatal(err)
}
}
func (d *Display) DisplayReactors(r map[uint32]*Device) {
// this func takes in a list of devices to update and loops over them
// works by padding list for entries not seen yet
for _, reactor := range r {
txt := fmt.Sprintf("%v %v", reactor.Id, reactor.Status)
indx := int(reactor.Index)
for indx + 2 >= d.ReactorList.GetItemCount() {
// this prevent overwriting quit entry
d.ReactorList.InsertItem(-3,txt,reactor.Data,rune(47+d.ReactorList.GetItemCount()),nil)
}
if indx + 2 < d.ReactorList.GetItemCount() {
d.ReactorList.SetItemText(indx,txt,reactor.Data)
}
}
}
func (d *Display) DisplayDevices(devs map[uint32]*Device) {
// going to just clear every time as we reload new dev lists anyway
// going to clear on every reactor selection to simplify
// can probably just load from SM to save system resources on spam reloading
for _, dev := range devs {
txt := fmt.Sprintf("0x%x %v %v",dev.Id,dev.Status,dev.Type)
indx := int(dev.Index)
for indx >= d.DeviceList.GetItemCount() {
d.DeviceList.AddItem(txt,dev.Data,rune(49+d.DeviceList.GetItemCount()), nil)
}
if indx < d.DeviceList.GetItemCount() {
d.DeviceList.SetItemText(indx,txt,dev.Data)
}
}
}
func (d *Display) SelectReactor(index int, main, data string, r rune) {
// called when reactor in list in selected
if main != "Quit" {
if main == "Refresh" {
// TODO
} else {
maintxt := strings.Split(main," ")
id := maintxt[0]
if id, err := strconv.ParseUint(id, 10, 32); err != nil {
log.Fatal(err)
} else {
d.SelectedReactor <-uint32(id)
}
}
}
}
func (d *Display) SelectDevice(index int, main, data string, r rune) {
// called when device is selected in sub menu
maintxt := strings.Split(main," ")
id := maintxt[0]
id = strings.Trim(id,"0x \n")
logging.Debug(logging.DClient,"Selected dev %v", id)
if id, err := strconv.ParseUint(id, 16, 32); err != nil {
logging.Debug(logging.DError, "Error parsing: %v", err)
os.Exit(1)
} else {
d.SelectedDevice <-uint32(id)
}
}