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 //*LocalView *TUIClient SelectedReactor <-chan uint32 SelectedDevice <-chan uint32 Err chan error } func NewTUI(ip string, port int, ifconfig string, ch chan error) *TUI { //r := make(map[uint32]*Reactor) t := &TUI{} //l := new(LocalView) //l.Reactors = r //t.LocalView = l 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() //go t.Refresh() } 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.DevicePages,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 <-t.SelectedDevice: // TODO case <-timer: // time to ping for status logging.Debug(logging.DClient, "%v getting reactor status", t.Id) t.App.QueueUpdateDraw(func() { t.UpdateDevices() }) } } } func (t *TUI) UpdateDevices(r ...uint32) { // get devices for the reactor and update the tui var id uint32 // see if there is a page being displayed if name, _ := t.Display.DevicePages.GetFrontPage(); name != "" { if tmp, err := strconv.ParseUint(name, 10, 32); err != nil { log.Fatal(err) } else { id = uint32(tmp) } } // overwrite if called as a func if len(r) > 0 { id = r[0] } devs, err := t.TUIClient.GetDevices(id) if err != nil { log.Fatal(err) } if id != 0 { // reactor specificed split devs 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, id) t.DisplayReactors(reactors) } else { t.DisplayReactors(devs) } } // display struct and logic type Display struct { App *tview.Application Flex *tview.Flex ReactorList *tview.List DevicePages *tview.Pages DeviceList map[string]*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() lists := make(map[string]*tview.List) d.DeviceList = lists d.ReactorList = tview.NewList()//.ShowSecondaryText(false) d.ReactorList.AddItem("Quit","Press (q) to quit",113,func() { d.App.Stop() os.Exit(0) }) d.DevicePages = tview.NewPages() d.ReactorList.SetTitle("Reactors").SetBorder(true) d.ReactorList.SetSelectedFunc(d.SelectReactor) d.DevicePages.SetTitle("Devices").SetBorder(true) 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) { // function to display reactor list to table //d.Lock() //defer d.Unlock() // locking may break the hell out of this gonna trust tview for _, reactor := range r { txt := fmt.Sprintf("%v %v", reactor.Id, reactor.Status) if d.ReactorList.GetItemCount() > int(reactor.Index) + 1 { d.ReactorList.RemoveItem(int(reactor.Index)) } d.ReactorList.InsertItem(int(reactor.Index),txt,reactor.Data,rune(49+reactor.Index),nil) } } func (d *Display) DisplayDevices(devs map[uint32]*Device, rid uint32) { //d.Lock() reactorPage := strconv.FormatUint(uint64(rid), 10) var reactorList *tview.List var ok bool if reactorList, ok = d.DeviceList[reactorPage]; !ok { reactorList = tview.NewList().ShowSecondaryText(false) d.DeviceList[reactorPage] = reactorList d.DevicePages.AddPage(reactorPage, reactorList, true, false) } //d.Unlock() for _, dev := range devs { txt := fmt.Sprintf("0x%x %v %v",dev.Id,dev.Status,dev.Type) // sensor alive at 0x0 data if reactorList.GetItemCount() > int(dev.Index) { reactorList.RemoveItem(int(dev.Index)) } reactorList.InsertItem(int(dev.Index),txt,dev.Data,0,nil) } d.DevicePages.SwitchToPage(reactorPage) } func (d *Display) SelectReactor(index int, main, data string, r rune) { // called when reactor in list in selected if main != "Quit" { 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, id string, r rune) { // called when device is selected in sub menu if id, err := strconv.ParseUint(id, 10, 32); err != nil { log.Fatal(err) } else { d.SelectedDevice <-uint32(id) } }