2022-01-07 14:54:33 +00:00
package steward
import (
"bufio"
2022-01-12 06:42:41 +00:00
"context"
2022-01-07 14:54:33 +00:00
"fmt"
2022-01-13 07:34:22 +00:00
"io"
2022-01-07 14:54:33 +00:00
"log"
"os"
2022-01-09 08:36:48 +00:00
"path/filepath"
2022-01-07 14:54:33 +00:00
"reflect"
2022-01-17 14:13:14 +00:00
"sort"
2022-01-07 14:54:33 +00:00
"strconv"
"strings"
"time"
2022-10-05 09:22:41 +00:00
"gopkg.in/yaml.v3"
2022-01-07 14:54:33 +00:00
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
2022-01-11 13:55:35 +00:00
// ---------------------------------------------------------------------
// Main structure
// ---------------------------------------------------------------------
2022-01-14 12:09:23 +00:00
// tui holds general types used within the tui.
2022-01-08 03:19:51 +00:00
type tui struct {
2022-01-31 07:49:46 +00:00
toConsoleCh chan [ ] byte
2022-01-12 06:42:41 +00:00
toRingbufferCh chan [ ] subjectAndMessage
ctx context . Context
2022-01-13 07:34:22 +00:00
nodeName Node
2022-01-07 14:54:33 +00:00
}
2022-01-14 12:09:23 +00:00
// newTui returns a new tui.
2022-01-13 07:34:22 +00:00
func newTui ( nodeName Node ) ( * tui , error ) {
2022-01-31 07:49:46 +00:00
ch := make ( chan [ ] byte )
2022-01-12 06:42:41 +00:00
s := tui {
toConsoleCh : ch ,
2022-01-13 07:34:22 +00:00
nodeName : nodeName ,
2022-01-12 06:42:41 +00:00
}
2022-01-07 14:54:33 +00:00
return & s , nil
}
2022-01-14 12:09:23 +00:00
// slide holds the information about a slide
2022-01-07 14:54:33 +00:00
type slide struct {
name string
key tcell . Key
primitive tview . Primitive
}
2022-01-14 12:09:23 +00:00
// Start will start the tui.
2022-01-12 06:42:41 +00:00
func ( t * tui ) Start ( ctx context . Context , toRingBufferCh chan [ ] subjectAndMessage ) error {
t . ctx = ctx
t . toRingbufferCh = toRingBufferCh
2022-01-07 14:54:33 +00:00
pages := tview . NewPages ( )
app := tview . NewApplication ( )
2022-01-11 13:55:35 +00:00
// Check if F key is pressed, and switch slide accordingly.
2022-01-07 14:54:33 +00:00
app . SetInputCapture ( func ( event * tcell . EventKey ) * tcell . EventKey {
2022-01-11 13:55:35 +00:00
switch event . Key ( ) {
case tcell . KeyF1 :
pages . SwitchToPage ( "console" )
2022-01-07 14:54:33 +00:00
return nil
2022-01-11 13:55:35 +00:00
case tcell . KeyF2 :
2022-01-07 14:54:33 +00:00
pages . SwitchToPage ( "message" )
return nil
2022-01-11 13:55:35 +00:00
case tcell . KeyF3 :
pages . SwitchToPage ( "info" )
return nil
2022-01-18 09:48:23 +00:00
case tcell . KeyCtrlC :
app . Stop ( )
log . Printf ( "info: detected ctrl+c, stopping TUI\n" )
2022-01-07 14:54:33 +00:00
}
return event
} )
info := tview . NewTextView ( ) .
SetDynamicColors ( true ) .
SetRegions ( true ) .
SetWrap ( false )
// The slides to draw, and their name.
// NB: This slice is being looped over further below, to create the menu
2022-01-09 08:36:48 +00:00
// elements. If adding a new slide, make sure that slides are ordered in
2022-01-07 14:54:33 +00:00
// chronological order, so we can auto generate the info menu with it's
// corresponding F key based on the slice index+1.
slides := [ ] slide {
2022-01-12 06:42:41 +00:00
{ name : "console" , key : tcell . KeyF1 , primitive : t . console ( app ) } ,
{ name : "message" , key : tcell . KeyF2 , primitive : t . messageSlide ( app ) } ,
{ name : "info" , key : tcell . KeyF3 , primitive : t . infoSlide ( app ) } ,
2022-01-07 14:54:33 +00:00
}
2022-01-11 09:49:24 +00:00
// Add a page for each slide.
2022-01-07 14:54:33 +00:00
for i , v := range slides {
if i == 0 {
pages . AddPage ( v . name , v . primitive , true , true )
fmt . Fprintf ( info , " F%v:%v " , i + 1 , v . name )
continue
}
pages . AddPage ( v . name , v . primitive , true , false )
fmt . Fprintf ( info , " F%v:%v " , i + 1 , v . name )
}
// Create the main layout.
layout := tview . NewFlex ( )
//layout.SetBorder(true)
layout . SetDirection ( tview . FlexRow ) .
AddItem ( pages , 0 , 10 , true ) .
AddItem ( info , 1 , 1 , false )
2022-01-09 08:36:48 +00:00
root := app . SetRoot ( layout , true )
root . EnableMouse ( true )
if err := root . Run ( ) ; err != nil {
log . Printf ( "error: root.Run(): %v\n" , err )
os . Exit ( 1 )
2022-01-07 14:54:33 +00:00
}
return nil
}
2022-01-11 13:55:35 +00:00
// ---------------------------------------------------------------------
// Slides
// ---------------------------------------------------------------------
2022-01-12 06:42:41 +00:00
func ( t * tui ) infoSlide ( app * tview . Application ) tview . Primitive {
2022-01-07 14:54:33 +00:00
flex := tview . NewFlex ( )
flex . SetTitle ( "info" )
flex . SetBorder ( true )
textView := tview . NewTextView ( )
flex . AddItem ( textView , 0 , 1 , false )
2022-01-18 13:29:24 +00:00
fmt . Fprintf ( textView , "Information page for Steward TUI.\n" )
2022-01-07 14:54:33 +00:00
return flex
}
2022-01-13 20:57:06 +00:00
// drawMessageInputFields
//
// Draw all the message input field with values on the screen.
//
// Loop trough all the fields of the Message struct, and create
// a an input field or dropdown selector for each field.
// If a field of the struct is not defined below, it will be
// created a "no defenition" element, so it we can easily spot
// Message fields who miss an item in the form.
//
// INFO: The reason that reflect are being used here is to have
// a simple way of detecting that we are creating form fields
// for all the fields in the struct. If we have forgot'en one
// it will create a "no case" field in the console, to easily
// detect that a struct field are missing a defenition below.
2022-01-17 13:20:03 +00:00
func drawMessageInputFields ( p slideMessageEdit , m tuiMessage ) {
2022-01-14 08:02:17 +00:00
fieldWidth := 0
2022-01-13 21:25:46 +00:00
2022-01-07 14:54:33 +00:00
mRefVal := reflect . ValueOf ( m )
for i := 0 ; i < mRefVal . NumField ( ) ; i ++ {
fieldName := mRefVal . Type ( ) . Field ( i ) . Name
switch fieldName {
2022-01-09 08:36:48 +00:00
case "_" :
2022-01-07 14:54:33 +00:00
case "ToNode" :
// Get nodes from file.
2022-01-09 08:36:48 +00:00
values , err := getNodeNames ( "nodeslist.cfg" )
2022-01-07 14:54:33 +00:00
if err != nil {
2022-01-09 08:36:48 +00:00
log . Printf ( "error: unable to open file: %v\n" , err )
2022-01-07 14:54:33 +00:00
}
2022-01-14 08:02:17 +00:00
if m . ToNode != nil && * m . ToNode != "" {
tmp := [ ] string { string ( * m . ToNode ) }
tmp = append ( tmp , values ... )
values = tmp
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddDropDown ( fieldName , values , 0 , nil ) . SetItemPadding ( 1 )
2022-01-07 14:54:33 +00:00
//c.msgForm.AddDropDown(mRefVal.Type().Field(i).Name, values, 0, nil).SetItemPadding(1)
2022-01-09 08:36:48 +00:00
case "ToNodes" :
2022-01-13 21:25:46 +00:00
if m . ToNodes == nil {
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , "" , fieldWidth , nil , nil )
2022-01-13 21:25:46 +00:00
continue
}
if len ( * m . ToNodes ) != 0 {
val1 := * m . ToNodes
var val2 string
for i , v := range val1 {
if i == 0 {
val2 = fmt . Sprintf ( "\"%v\"" , v )
continue
}
val2 = fmt . Sprintf ( "%v,\"%v\"" , val2 , v )
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , val2 , fieldWidth , nil , nil )
2022-01-13 21:25:46 +00:00
}
2022-01-07 14:54:33 +00:00
case "Method" :
2022-01-14 08:02:17 +00:00
var v Method
ma := v . GetMethodsAvailable ( )
2022-01-07 14:54:33 +00:00
values := [ ] string { }
for k := range ma . Methodhandlers {
values = append ( values , string ( k ) )
}
2022-01-14 08:02:17 +00:00
if m . Method != nil && * m . Method != "" {
tmp := [ ] string { string ( * m . Method ) }
tmp = append ( tmp , values ... )
values = tmp
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddDropDown ( fieldName , values , 0 , nil ) . SetItemPadding ( 1 )
2022-01-09 08:36:48 +00:00
case "MethodArgs" :
2022-01-13 21:25:46 +00:00
if m . MethodArgs == nil {
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , "" , 0 , nil , nil )
2022-01-13 21:25:46 +00:00
continue
}
if len ( * m . MethodArgs ) != 0 {
val1 := * m . MethodArgs
var val2 string
for i , v := range val1 {
if i == 0 {
val2 = fmt . Sprintf ( "\"%v\"" , v )
continue
}
val2 = fmt . Sprintf ( "%v,\"%v\"" , val2 , v )
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , val2 , 0 , nil , nil )
2022-01-13 21:25:46 +00:00
}
2022-01-07 14:54:33 +00:00
case "ReplyMethod" :
2022-01-14 08:02:17 +00:00
var v Method
rm := v . GetReplyMethods ( )
2022-01-07 14:54:33 +00:00
values := [ ] string { }
for _ , k := range rm {
values = append ( values , string ( k ) )
}
2022-01-14 08:02:17 +00:00
if m . ReplyMethod != nil && * m . ReplyMethod != "" {
tmp := [ ] string { string ( * m . ReplyMethod ) }
tmp = append ( tmp , values ... )
values = tmp
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddDropDown ( fieldName , values , 0 , nil ) . SetItemPadding ( 1 )
2022-01-09 08:36:48 +00:00
case "ReplyMethodArgs" :
2022-01-13 21:25:46 +00:00
if m . ReplyMethodArgs == nil {
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , "" , fieldWidth , nil , nil )
2022-01-13 21:25:46 +00:00
continue
}
if len ( * m . ReplyMethodArgs ) != 0 {
val1 := * m . ReplyMethodArgs
var val2 string
for i , v := range val1 {
if i == 0 {
val2 = fmt . Sprintf ( "\"%v\"" , v )
continue
}
val2 = fmt . Sprintf ( "%v,\"%v\"" , val2 , v )
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , val2 , fieldWidth , nil , nil )
2022-01-13 21:25:46 +00:00
}
2022-01-07 14:54:33 +00:00
case "ACKTimeout" :
value := 30
2022-01-14 08:02:17 +00:00
if m . ACKTimeout != nil && * m . ACKTimeout != 0 {
value = * m . ACKTimeout
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-07 14:54:33 +00:00
case "Retries" :
value := 1
2022-01-14 08:02:17 +00:00
if m . Retries != nil && * m . Retries != 0 {
value = * m . Retries
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-07 14:54:33 +00:00
case "ReplyACKTimeout" :
value := 30
2022-01-14 08:02:17 +00:00
if m . ReplyACKTimeout != nil && * m . ReplyACKTimeout != 0 {
value = * m . ReplyACKTimeout
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-07 14:54:33 +00:00
case "ReplyRetries" :
value := 1
2022-01-14 08:02:17 +00:00
if m . ReplyRetries != nil && * m . ReplyRetries != 0 {
value = * m . ReplyRetries
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-07 14:54:33 +00:00
case "MethodTimeout" :
value := 120
2022-01-14 08:02:17 +00:00
if m . MethodTimeout != nil && * m . MethodTimeout != 0 {
value = * m . MethodTimeout
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-09 08:36:48 +00:00
case "ReplyMethodTimeout" :
value := 120
2022-01-14 08:02:17 +00:00
if m . ReplyMethodTimeout != nil && * m . ReplyMethodTimeout != 0 {
value = * m . ReplyMethodTimeout
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , fmt . Sprintf ( "%d" , value ) , fieldWidth , validateInteger , nil )
2022-01-07 14:54:33 +00:00
case "Directory" :
value := "/some-dir/"
2022-01-14 08:02:17 +00:00
if m . Directory != nil && * m . Directory != "" {
value = * m . Directory
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , value , fieldWidth , nil , nil )
2022-01-07 14:54:33 +00:00
case "FileName" :
value := ".log"
2022-01-14 08:02:17 +00:00
if m . FileName != nil && * m . FileName != "" {
value = * m . FileName
}
2022-01-17 13:20:03 +00:00
p . inputForm . AddInputField ( fieldName , value , fieldWidth , nil , nil )
2022-01-09 08:36:48 +00:00
2022-01-07 14:54:33 +00:00
default :
// Add a no definition fields to the form if a a field within the
// struct were missing an action above, so we can easily detect
// if there is missing a case action for one of the struct fields.
2022-01-09 08:36:48 +00:00
2022-01-17 13:20:03 +00:00
p . inputForm . AddDropDown ( "error: no case for: " + fieldName , [ ] string { "1" , "2" } , 0 , nil ) . SetItemPadding ( 1 )
2022-01-07 14:54:33 +00:00
}
}
2022-01-13 20:57:06 +00:00
}
// pageMessage is a struct for holding all the main forms and
// views used in the message slide, so we can easily reference
// them later in the code.
2022-01-17 13:20:03 +00:00
type slideMessageEdit struct {
2022-01-18 11:12:25 +00:00
flex * tview . Flex
selectMessage * tview . Form
inputForm * tview . Form
outputForm * tview . TextView
logForm * tview . TextView
saveForm * tview . Form
2022-01-13 20:57:06 +00:00
}
2022-01-14 12:09:23 +00:00
// messageSlide is the main function for setting up the slides.
2022-01-13 20:57:06 +00:00
func ( t * tui ) messageSlide ( app * tview . Application ) tview . Primitive {
2022-01-17 13:20:03 +00:00
p := slideMessageEdit { }
2022-01-13 20:57:06 +00:00
2022-01-18 11:12:25 +00:00
p . selectMessage = tview . NewForm ( )
p . selectMessage . SetBorder ( true ) . SetTitle ( "Select Message" ) . SetTitleAlign ( tview . AlignLeft )
2022-01-17 13:20:03 +00:00
p . inputForm = tview . NewForm ( )
p . inputForm . SetBorder ( true ) . SetTitle ( "Message input" ) . SetTitleAlign ( tview . AlignLeft )
2022-01-13 20:57:06 +00:00
2022-01-17 13:20:03 +00:00
p . outputForm = tview . NewTextView ( )
p . outputForm . SetBorder ( true ) . SetTitle ( "Message output" ) . SetTitleAlign ( tview . AlignLeft )
p . outputForm . SetChangedFunc ( func ( ) {
2022-01-13 20:57:06 +00:00
// Will cause the log window to be redrawn as soon as
// new output are detected.
app . Draw ( )
} )
p . logForm = tview . NewTextView ( )
p . logForm . SetBorder ( true ) . SetTitle ( "Log/Status" ) . SetTitleAlign ( tview . AlignLeft )
p . logForm . SetChangedFunc ( func ( ) {
// Will cause the log window to be redrawn as soon as
// new output are detected.
app . Draw ( )
} )
p . saveForm = tview . NewForm ( )
p . saveForm . SetBorder ( true ) . SetTitle ( "Save message" ) . SetTitleAlign ( tview . AlignLeft )
// Create a flex layout.
//
// Create the outer flex layout.
p . flex = tview . NewFlex ( ) . SetDirection ( tview . FlexRow ) .
// Add a flex for the top windows with columns.
AddItem ( tview . NewFlex ( ) . SetDirection ( tview . FlexColumn ) .
2022-01-18 11:12:25 +00:00
// Add a new flex for splitting output form horizontally.
AddItem ( tview . NewFlex ( ) . SetDirection ( tview . FlexRow ) .
// Add the select message form.
2022-01-18 13:29:24 +00:00
AddItem ( p . selectMessage , 5 , 0 , false ) .
2022-01-18 11:12:25 +00:00
// Add the input message form.
AddItem ( p . inputForm , 0 , 10 , false ) ,
0 , 10 , false ) .
2022-01-13 20:57:06 +00:00
// Add a new flex for splitting output form horizontally.
AddItem ( tview . NewFlex ( ) . SetDirection ( tview . FlexRow ) .
// Add the message output form.
2022-01-17 13:20:03 +00:00
AddItem ( p . outputForm , 0 , 10 , false ) .
2022-01-13 20:57:06 +00:00
// Add the save message form.
AddItem ( p . saveForm , 0 , 2 , false ) ,
0 , 10 , false ) ,
0 , 10 , false ) .
// Add a flex for the bottom log window.
AddItem ( tview . NewFlex ( ) .
// Add the log form.
2022-01-18 13:29:24 +00:00
AddItem ( p . logForm , 0 , 1 , false ) ,
2022-01-18 13:46:02 +00:00
0 , 1 , false )
2022-01-13 20:57:06 +00:00
m := tuiMessage { }
2022-01-14 11:58:08 +00:00
// ---
// Add a dropdown menu to select message files to use.
2022-01-26 08:23:02 +00:00
msgsValues := t . getMessageNames ( p . logForm )
2022-01-14 11:58:08 +00:00
2022-01-18 13:29:24 +00:00
msgDropdownFunc := func ( msgFileName string , index int ) {
2022-01-14 11:58:08 +00:00
filePath := filepath . Join ( "messages" , msgFileName )
fh , err := os . Open ( filePath )
if err != nil {
fmt . Fprintf ( p . logForm , "error: failed to open message file: %v\n" , err )
return
}
defer fh . Close ( )
fileContent , err := io . ReadAll ( fh )
if err != nil {
fmt . Fprintf ( p . logForm , "error: failed to read message file: %v\n" , err )
return
}
var msgs [ ] tuiMessage
2022-10-05 09:22:41 +00:00
err = yaml . Unmarshal ( fileContent , & msgs )
2022-01-14 11:58:08 +00:00
if err != nil {
2022-10-05 09:22:41 +00:00
fmt . Fprintf ( p . logForm , "error: yaml unmarshal of file content failed: %v\n" , err )
2022-01-14 11:58:08 +00:00
return
}
m = msgs [ 0 ]
// Clear the form.
2022-01-17 13:20:03 +00:00
p . inputForm . Clear ( false )
2022-01-18 13:29:24 +00:00
// Draw all the message input field with values on the screen.
p . inputForm . Clear ( false )
2022-01-14 11:58:08 +00:00
drawMessageInputFields ( p , m )
2022-01-18 13:29:24 +00:00
}
messageDropdown := tview . NewDropDown ( )
messageDropdown . SetLabelColor ( tcell . ColorIndianRed )
messageDropdown . SetLabel ( "message" ) . SetOptions ( msgsValues , msgDropdownFunc )
2022-01-17 21:24:37 +00:00
// Clear the form.
p . inputForm . Clear ( false )
2022-01-24 07:11:23 +00:00
drawMessageInputFields ( p , m )
2022-01-18 11:12:25 +00:00
p . selectMessage . AddFormItem ( messageDropdown )
2022-01-17 07:04:43 +00:00
2022-01-17 13:20:03 +00:00
p . inputForm . AddButton ( "update message dropdown menu" , func ( ) {
2022-01-26 08:23:02 +00:00
messageMessageValues := t . getMessageNames ( p . logForm )
2022-01-18 13:29:24 +00:00
messageDropdown . SetLabel ( "message" ) . SetOptions ( messageMessageValues , msgDropdownFunc )
2022-01-17 07:04:43 +00:00
} )
2022-01-14 11:58:08 +00:00
// ---
2022-01-11 09:49:24 +00:00
// Variable to hold the last output created when the generate button have
// been pushed.
var lastGeneratedMessage [ ] byte
var saveFileName string
2022-01-07 14:54:33 +00:00
// Add Buttons below the message fields. Like Generate and Exit.
2022-01-17 13:20:03 +00:00
p . inputForm .
2022-01-07 14:54:33 +00:00
// Add a generate button, which when pressed will loop through all the
// message form items, and if found fill the value into a msg struct,
// and at last write it to a file.
AddButton ( "generate to console" , func ( ) {
2022-01-17 13:20:03 +00:00
p . outputForm . Clear ( )
2022-01-07 14:54:33 +00:00
2022-01-10 12:21:30 +00:00
m := tuiMessage { }
2022-01-09 08:36:48 +00:00
// Loop trough all the form fields, check the value of each
// form field, and add the value to m.
2022-01-17 13:20:03 +00:00
for i := 0 ; i < p . inputForm . GetFormItemCount ( ) ; i ++ {
fi := p . inputForm . GetFormItem ( i )
2022-01-07 14:54:33 +00:00
label , value := getLabelAndValue ( fi )
switch label {
2022-01-14 11:58:08 +00:00
case "message" :
2022-01-07 14:54:33 +00:00
case "ToNode" :
2022-01-10 12:21:30 +00:00
v := Node ( value )
m . ToNode = & v
2022-01-09 08:36:48 +00:00
case "ToNodes" :
2022-01-10 12:21:30 +00:00
slice , err := stringToNode ( value )
if err != nil {
2022-01-13 21:25:46 +00:00
fmt . Fprintf ( p . logForm , "%v : error: ToNodes missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , err )
2022-01-10 12:21:30 +00:00
return
2022-01-07 14:54:33 +00:00
}
2022-01-10 12:21:30 +00:00
m . ToNodes = slice
2022-01-07 14:54:33 +00:00
case "Method" :
2022-01-10 12:21:30 +00:00
v := Method ( value )
m . Method = & v
2022-01-09 08:36:48 +00:00
case "MethodArgs" :
2022-01-10 12:21:30 +00:00
slice , err := stringToSlice ( value )
if err != nil {
2022-01-13 21:25:46 +00:00
fmt . Fprintf ( p . logForm , "%v : error: MethodArgs missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , err )
2022-01-10 12:21:30 +00:00
return
2022-01-09 08:36:48 +00:00
}
2022-01-10 12:21:30 +00:00
m . MethodArgs = slice
2022-01-07 14:54:33 +00:00
case "ReplyMethod" :
2022-01-10 12:21:30 +00:00
v := Method ( value )
m . ReplyMethod = & v
2022-01-09 08:36:48 +00:00
case "ReplyMethodArgs" :
2022-01-10 12:21:30 +00:00
slice , err := stringToSlice ( value )
if err != nil {
fmt . Fprintf ( p . logForm , "%v : error: ReplyMethodArgs missing or malformed format, should be \"arg0\",\"arg1\",\"arg2\", %v\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , err )
return
2022-01-09 08:36:48 +00:00
}
2022-01-10 12:21:30 +00:00
m . ReplyMethodArgs = slice
2022-01-07 14:54:33 +00:00
case "ACKTimeout" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . ACKTimeout = & v
2022-01-07 14:54:33 +00:00
case "Retries" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . Retries = & v
2022-01-07 14:54:33 +00:00
case "ReplyACKTimeout" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . ReplyACKTimeout = & v
2022-01-07 14:54:33 +00:00
case "ReplyRetries" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . ReplyRetries = & v
2022-01-07 14:54:33 +00:00
case "MethodTimeout" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . MethodTimeout = & v
2022-01-09 08:36:48 +00:00
case "ReplyMethodTimeout" :
v , _ := strconv . Atoi ( value )
2022-01-10 12:21:30 +00:00
m . ReplyMethodTimeout = & v
2022-01-07 14:54:33 +00:00
case "Directory" :
2022-01-10 12:21:30 +00:00
m . Directory = & value
2022-01-07 14:54:33 +00:00
case "FileName" :
2022-01-10 12:21:30 +00:00
m . FileName = & value
2022-01-07 14:54:33 +00:00
default :
2022-01-09 08:36:48 +00:00
fmt . Fprintf ( p . logForm , "%v : error: did not find case definition for how to handle the \"%v\" within the switch statement\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , label )
2022-01-10 12:21:30 +00:00
return
2022-01-07 14:54:33 +00:00
}
}
2022-01-10 12:21:30 +00:00
msgs := [ ] tuiMessage { }
2022-01-07 14:54:33 +00:00
msgs = append ( msgs , m )
2022-10-05 09:22:41 +00:00
// // msgsIndented, err := json.MarshalIndent(msgs, "", " ")
// buf := new(bytes.Buffer)
// enc := json.NewEncoder(buf)
// enc.SetEscapeHTML(false)
// enc.SetIndent("", " ")
// err := enc.Encode(msgs)
// if err != nil {
// fmt.Fprintf(p.logForm, "%v : error: jsonIndent failed: %v\n", time.Now().Format("Mon Jan _2 15:04:05 2006"), err)
// }
bs , err := yaml . Marshal ( msgs )
2022-01-07 14:54:33 +00:00
if err != nil {
2022-10-05 09:22:41 +00:00
fmt . Fprintf ( p . logForm , "%v : error: yaml marshal failed: %v\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , err )
2022-01-07 14:54:33 +00:00
}
2022-01-11 09:49:24 +00:00
// Copy the message to a variable outside this scope so we can use
// the content for example if we want to save the message to file.
2022-10-05 09:22:41 +00:00
lastGeneratedMessage = bs
2022-01-11 09:49:24 +00:00
2022-10-05 09:22:41 +00:00
_ , err = p . outputForm . Write ( bs )
2022-01-07 14:54:33 +00:00
if err != nil {
fmt . Fprintf ( p . logForm , "%v : error: write to fh failed: %v\n" , time . Now ( ) . Format ( "Mon Jan _2 15:04:05 2006" ) , err )
}
} ) .
// Add exit button.
AddButton ( "exit" , func ( ) {
app . Stop ( )
} )
2022-01-17 21:24:37 +00:00
// app.SetFocus(p.inputForm)
2022-01-07 14:54:33 +00:00
2022-01-11 09:49:24 +00:00
p . saveForm .
AddInputField ( "FileName" , "" , 40 , nil , func ( text string ) {
saveFileName = text
} ) .
AddButton ( "save" , func ( ) {
messageFolder := "messages"
if saveFileName == "" {
fmt . Fprintf ( p . logForm , "error: missing filename\n" )
return
}
if _ , err := os . Stat ( messageFolder ) ; os . IsNotExist ( err ) {
2023-01-10 05:50:28 +00:00
err := os . MkdirAll ( messageFolder , 0770 )
2022-01-11 09:49:24 +00:00
if err != nil {
fmt . Fprintf ( p . logForm , "error: failed to create messages folder: %v\n" , err )
return
}
}
file := filepath . Join ( messageFolder , saveFileName )
fh , err := os . OpenFile ( file , os . O_CREATE | os . O_RDWR | os . O_TRUNC , 0755 )
if err != nil {
fmt . Fprintf ( p . logForm , "error: opening file for writing: %v\n" , err )
return
}
defer fh . Close ( )
_ , err = fh . Write ( [ ] byte ( lastGeneratedMessage ) )
if err != nil {
fmt . Fprintf ( p . logForm , "error: writing message to file: %v\n" , err )
return
}
2022-01-11 13:55:35 +00:00
fmt . Fprintf ( p . logForm , "info: succesfully wrote message to file: %v\n" , file )
2022-01-17 07:04:43 +00:00
// update the select message dropdown
2022-01-26 08:23:02 +00:00
messageMessageValues := t . getMessageNames ( p . logForm )
2022-01-18 13:29:24 +00:00
messageDropdown . SetLabel ( "message" ) . SetOptions ( messageMessageValues , msgDropdownFunc )
2022-01-24 07:11:23 +00:00
// p.inputForm.Clear(false)
2022-01-17 07:04:43 +00:00
2022-01-11 09:49:24 +00:00
} )
2022-01-07 14:54:33 +00:00
return p . flex
}
2022-01-12 06:42:41 +00:00
func ( t * tui ) console ( app * tview . Application ) tview . Primitive {
2022-01-11 13:55:35 +00:00
// pageMessage is a struct for holding all the main forms and
// views used in the message slide, so we can easily reference
// them later in the code.
2022-01-17 13:20:03 +00:00
type slideConsole struct {
2022-01-11 13:55:35 +00:00
flex * tview . Flex
selectForm * tview . Form
outputForm * tview . TextView
}
2022-01-17 13:20:03 +00:00
p := slideConsole { }
2022-01-11 13:55:35 +00:00
p . selectForm = tview . NewForm ( )
p . selectForm . SetBorder ( true ) . SetTitle ( "select" ) . SetTitleAlign ( tview . AlignLeft )
2022-01-18 13:46:02 +00:00
p . selectForm . SetButtonsAlign ( tview . AlignCenter )
p . selectForm . SetHorizontal ( false )
2022-01-11 13:55:35 +00:00
p . outputForm = tview . NewTextView ( )
p . outputForm . SetBorder ( true ) . SetTitle ( "output" ) . SetTitleAlign ( tview . AlignLeft )
p . outputForm . SetChangedFunc ( func ( ) {
// Will cause the log window to be redrawn as soon as
// new output are detected.
app . Draw ( )
} )
// Create a flex layout.
//
// Create the outer flex layout.
p . flex = tview . NewFlex ( ) . SetDirection ( tview . FlexRow ) .
// Add a flex for the top windows with columns.
AddItem ( tview . NewFlex ( ) . SetDirection ( tview . FlexColumn ) .
AddItem ( p . selectForm , 0 , 3 , false ) .
// Add the message output form.
AddItem ( p . outputForm , 0 , 10 , false ) ,
0 , 10 , false )
// Add a flex for the bottom log window.
// Add items.
// Create nodes dropdown field.
nodesList , err := getNodeNames ( "nodeslist.cfg" )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: failed to open nodeslist.cfg file\n" )
}
2022-01-12 04:44:41 +00:00
nodesDropdown := tview . NewDropDown ( )
nodesDropdown . SetLabelColor ( tcell . ColorIndianRed )
nodesDropdown . SetLabel ( "nodes" ) . SetOptions ( nodesList , nil )
p . selectForm . AddFormItem ( nodesDropdown )
2022-01-26 08:23:02 +00:00
msgsValues := t . getMessageNames ( p . outputForm )
2022-01-12 04:44:41 +00:00
2022-01-17 07:04:43 +00:00
messageDropdown := tview . NewDropDown ( )
messageDropdown . SetLabelColor ( tcell . ColorIndianRed )
messageDropdown . SetLabel ( "message" ) . SetOptions ( msgsValues , nil )
p . selectForm . AddFormItem ( messageDropdown )
2022-01-12 05:16:14 +00:00
// Add button for manually updating dropdown menus.
2022-01-18 13:46:02 +00:00
p . selectForm . AddButton ( "update" , func ( ) {
2022-01-12 05:16:14 +00:00
nodesList , err := getNodeNames ( "nodeslist.cfg" )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: failed to open nodeslist.cfg file\n" )
}
nodesDropdown . SetLabel ( "nodes" ) . SetOptions ( nodesList , nil )
2022-01-26 08:23:02 +00:00
msgsValues := t . getMessageNames ( p . outputForm )
2022-01-17 07:04:43 +00:00
messageDropdown . SetLabel ( "message" ) . SetOptions ( msgsValues , nil )
2022-01-12 04:44:41 +00:00
} )
2022-01-11 13:55:35 +00:00
2022-01-18 13:46:02 +00:00
// Add button for clearing the output form.
p . selectForm . AddButton ( "clear" , func ( ) {
p . outputForm . Clear ( )
} )
2022-01-12 05:16:14 +00:00
// Update the dropdown menus when the flex view gets focus.
p . flex . SetFocusFunc ( func ( ) {
2022-01-12 04:44:41 +00:00
nodesList , err := getNodeNames ( "nodeslist.cfg" )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: failed to open nodeslist.cfg file\n" )
}
nodesDropdown . SetLabel ( "nodes" ) . SetOptions ( nodesList , nil )
2022-01-11 13:55:35 +00:00
2022-01-26 08:23:02 +00:00
messageValues := t . getMessageNames ( p . outputForm )
2022-01-17 07:04:43 +00:00
messageDropdown . SetLabel ( "message" ) . SetOptions ( messageValues , nil )
2022-01-12 04:44:41 +00:00
} )
2022-01-11 13:55:35 +00:00
p . selectForm . AddButton ( "send message" , func ( ) {
2022-01-13 07:34:22 +00:00
// here........
2022-01-17 07:04:43 +00:00
nr , msgFileName := messageDropdown . GetCurrentOption ( )
2022-01-13 07:34:22 +00:00
if nr < 1 {
fmt . Fprintf ( p . outputForm , "info: please select a message from the dropdown: %v\n" , msgFileName )
return
}
nr , toNode := nodesDropdown . GetCurrentOption ( )
if nr < 1 {
fmt . Fprintf ( p . outputForm , "info: please select a message from the dropdown: %v\n" , msgFileName )
return
}
filePath := filepath . Join ( "messages" , msgFileName )
fh , err := os . Open ( filePath )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: failed to open message file: %v\n" , err )
return
}
defer fh . Close ( )
fileContent , err := io . ReadAll ( fh )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: failed to read message file: %v\n" , err )
return
}
var msgs [ ] Message
2022-10-05 09:22:41 +00:00
err = yaml . Unmarshal ( fileContent , & msgs )
2022-01-13 07:34:22 +00:00
if err != nil {
2022-10-05 09:22:41 +00:00
fmt . Fprintf ( p . outputForm , "error: yaml unmarshal of file content failed: %v\n" , err )
2022-01-13 07:34:22 +00:00
return
}
msg := msgs [ 0 ]
msg . FromNode = t . nodeName
msg . ToNode = Node ( toNode )
// fmt.Fprintf(p.outputForm, "%#v\n", msg)
sam , err := newSubjectAndMessage ( msg )
if err != nil {
fmt . Fprintf ( p . outputForm , "error: newSubjectAndMessage failed: %v\n" , err )
return
}
sams := [ ] subjectAndMessage { sam }
t . toRingbufferCh <- sams
2022-01-11 13:55:35 +00:00
} )
2022-01-12 06:42:41 +00:00
go func ( ) {
for {
select {
case messageData := <- t . toConsoleCh :
for _ , v := range messageData {
2022-10-14 08:12:50 +00:00
fmt . Fprintf ( p . outputForm , "%v" , string ( v ) )
2022-01-12 06:42:41 +00:00
}
case <- t . ctx . Done ( ) :
log . Printf ( "info: stopped tui toConsole worker\n" )
return
}
}
} ( )
2022-01-11 13:55:35 +00:00
return p . flex
}
// ---------------------------------------------------------------------
// Helper functions
// ---------------------------------------------------------------------
2022-01-14 12:09:23 +00:00
// getMessageNames will get the names of all the messages in
// the messages folder.
2022-01-26 08:23:02 +00:00
func ( t * tui ) getMessageNames ( outputForm * tview . TextView ) [ ] string {
2022-01-12 04:44:41 +00:00
// Create messages dropdown field.
2022-09-06 08:59:07 +00:00
fInfo , err := os . ReadDir ( "messages" )
2022-01-12 04:44:41 +00:00
if err != nil {
fmt . Fprintf ( outputForm , "error: failed to read files from messages dir\n" )
}
msgsValues := [ ] string { }
msgsValues = append ( msgsValues , "" )
for _ , v := range fInfo {
msgsValues = append ( msgsValues , v . Name ( ) )
}
return msgsValues
}
2022-01-10 12:21:30 +00:00
// stringToSlice will Split the comma separated string
// into a and remove the start and end ampersand.
func stringToSlice ( s string ) ( * [ ] string , error ) {
if s == "" {
return nil , nil
}
var stringSlice [ ] string
sp := strings . Split ( s , "," )
for _ , v := range sp {
// Check if format is correct, return if not.
pre := strings . HasPrefix ( v , "\"" )
suf := strings . HasSuffix ( v , "\"" )
if ! pre || ! suf {
return nil , fmt . Errorf ( "stringToSlice: missing leading or ending ampersand" )
}
// Remove leading and ending ampersand.
v = v [ 1 : ]
v = strings . TrimSuffix ( v , "\"" )
stringSlice = append ( stringSlice , v )
}
return & stringSlice , nil
}
// stringToNodes will Split the comma separated slice
// of nodes, and remove the start and end ampersand.
func stringToNode ( s string ) ( * [ ] Node , error ) {
if s == "" {
return nil , nil
}
var nodeSlice [ ] Node
sp := strings . Split ( s , "," )
for _ , v := range sp {
// Check if format is correct, return if not.
pre := strings . HasPrefix ( v , "\"" )
suf := strings . HasSuffix ( v , "\"" )
if ! pre || ! suf {
return nil , fmt . Errorf ( "stringToSlice: missing leading or ending ampersand" )
}
// Remove leading and ending ampersand.
v = v [ 1 : ]
v = strings . TrimSuffix ( v , "\"" )
nodeSlice = append ( nodeSlice , Node ( v ) )
}
return & nodeSlice , nil
}
2022-01-07 14:54:33 +00:00
// Will return the Label And the text Value of an input or dropdown form field.
func getLabelAndValue ( fi tview . FormItem ) ( string , string ) {
var label string
var value string
switch v := fi . ( type ) {
case * tview . InputField :
value = v . GetText ( )
label = v . GetLabel ( )
case * tview . DropDown :
label = v . GetLabel ( )
_ , value = v . GetCurrentOption ( )
}
return label , value
}
// Check if number is int.
func validateInteger ( text string , ch rune ) bool {
if text == "-" {
return true
}
_ , err := strconv . Atoi ( text )
return err == nil
}
// getNodes will load all the node names from a file, and return a slice of
// string values, each representing a unique node.
2022-01-09 08:36:48 +00:00
func getNodeNames ( fileName string ) ( [ ] string , error ) {
dirPath , err := os . Getwd ( )
if err != nil {
return nil , fmt . Errorf ( "error: tui: unable to get working directory: %v" , err )
}
filePath := filepath . Join ( dirPath , fileName )
2022-01-07 14:54:33 +00:00
fh , err := os . Open ( filePath )
if err != nil {
2022-01-09 08:36:48 +00:00
return nil , fmt . Errorf ( "error: tui: you should create a file named nodeslist.cfg with all your nodes : %v" , err )
2022-01-07 14:54:33 +00:00
}
defer fh . Close ( )
nodes := [ ] string { }
2022-01-11 09:49:24 +00:00
// append a blank node at the beginning of the slice, so the dropdown
// can be set to blank
nodes = append ( nodes , "" )
2022-01-07 14:54:33 +00:00
scanner := bufio . NewScanner ( fh )
for scanner . Scan ( ) {
node := scanner . Text ( )
nodes = append ( nodes , node )
}
2022-01-17 14:13:14 +00:00
sort . Strings ( nodes )
2022-01-07 14:54:33 +00:00
return nodes , nil
}