ssm-browse: fixed the views of dynamo-browse

This commit is contained in:
Leon Mika 2022-03-29 15:46:18 +11:00
parent f6f06eb22d
commit d3f6475070
6 changed files with 79 additions and 96 deletions

View file

@ -9,6 +9,7 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/common/ui/commandctrl"
"github.com/lmika/awstools/internal/common/ui/logging"
"github.com/lmika/awstools/internal/dynamo-browse/controllers" "github.com/lmika/awstools/internal/dynamo-browse/controllers"
"github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo" "github.com/lmika/awstools/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/lmika/awstools/internal/dynamo-browse/services/tables"
@ -46,10 +47,18 @@ func main() {
tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable) tableWriteController := controllers.NewTableWriteController(tableService, tableReadController, *flagTable)
_ = tableWriteController _ = tableWriteController
commandController := commandctrl.NewCommandController(map[string]commandctrl.Command{ commandController := commandctrl.NewCommandController()
"q": commandctrl.NoArgCommand(tea.Quit), commandController.AddCommands(&commandctrl.CommandContext{
//"rw": tableWriteController.ToggleReadWrite(), Commands: map[string]commandctrl.Command{
//"dup": tableWriteController.Duplicate(), "q": commandctrl.NoArgCommand(tea.Quit),
"table": func(args []string) tea.Cmd {
if len(args) == 0 {
return tableReadController.ListTables()
} else {
return tableReadController.ScanTable(args[0])
}
},
},
}) })
model := ui.NewModel(tableReadController, commandController) model := ui.NewModel(tableReadController, commandController)
@ -59,12 +68,8 @@ func main() {
p := tea.NewProgram(model, tea.WithAltScreen()) p := tea.NewProgram(model, tea.WithAltScreen())
f, err := tea.LogToFile("debug.log", "debug") closeFn := logging.EnableLogging()
if err != nil { defer closeFn()
fmt.Println("fatal:", err)
os.Exit(1)
}
defer f.Close()
log.Println("launching") log.Println("launching")
if err := p.Start(); err != nil { if err := p.Start(); err != nil {
@ -72,12 +77,3 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
//
//type msgLoopback struct {
// program *tea.Program
//}
//
//func (m *msgLoopback) Send(msg tea.Msg) {
// m.program.Send(msg)
//}

View file

@ -7,30 +7,36 @@ import (
"github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/lmika/awstools/internal/dynamo-browse/services/tables"
"github.com/pkg/errors" "github.com/pkg/errors"
"sync"
) )
type TableReadController struct { type TableReadController struct {
tableService *tables.Service tableService *tables.Service
tableName string tableName string
// state
mutex *sync.Mutex
resultSet *models.ResultSet
} }
func NewTableReadController(tableService *tables.Service, tableName string) *TableReadController { func NewTableReadController(tableService *tables.Service, tableName string) *TableReadController {
return &TableReadController{ return &TableReadController{
tableService: tableService, tableService: tableService,
tableName: tableName, tableName: tableName,
mutex: new(sync.Mutex),
} }
} }
// Init does an initial scan of the table. If no table is specified, it prompts for a table, then does a scan. // Init does an initial scan of the table. If no table is specified, it prompts for a table, then does a scan.
func (c *TableReadController) Init() tea.Cmd { func (c *TableReadController) Init() tea.Cmd {
if c.tableName == "" { if c.tableName == "" {
return c.listTables() return c.ListTables()
} else { } else {
return c.scanTable(c.tableName) return c.ScanTable(c.tableName)
} }
} }
func (c *TableReadController) listTables() tea.Cmd { func (c *TableReadController) ListTables() tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
tables, err := c.tableService.ListTables(context.Background()) tables, err := c.tableService.ListTables(context.Background())
if err != nil { if err != nil {
@ -40,13 +46,13 @@ func (c *TableReadController) listTables() tea.Cmd {
return PromptForTableMsg{ return PromptForTableMsg{
Tables: tables, Tables: tables,
OnSelected: func(tableName string) tea.Cmd { OnSelected: func(tableName string) tea.Cmd {
return c.scanTable(tableName) return c.ScanTable(tableName)
}, },
} }
} }
} }
func (c *TableReadController) scanTable(name string) tea.Cmd { func (c *TableReadController) ScanTable(name string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
ctx := context.Background() ctx := context.Background()
@ -60,23 +66,31 @@ func (c *TableReadController) scanTable(name string) tea.Cmd {
return events.Error(err) return events.Error(err)
} }
return NewResultSet{resultSet} return c.setResultSet(resultSet)
} }
} }
func (c *TableReadController) Rescan(resultSet *models.ResultSet) tea.Cmd { func (c *TableReadController) Rescan() tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
ctx := context.Background() ctx := context.Background()
resultSet, err := c.tableService.Scan(ctx, resultSet.TableInfo) resultSet, err := c.tableService.Scan(ctx, c.resultSet.TableInfo)
if err != nil { if err != nil {
return events.Error(err) return events.Error(err)
} }
return NewResultSet{resultSet} return c.setResultSet(resultSet)
} }
} }
func (c *TableReadController) setResultSet(resultSet *models.ResultSet) tea.Msg {
c.mutex.Lock()
defer c.mutex.Unlock()
c.resultSet = resultSet
return NewResultSet{resultSet}
}
/* /*
func (c *TableReadController) Scan() uimodels.Operation { func (c *TableReadController) Scan() uimodels.Operation {
return uimodels.OperationFn(func(ctx context.Context) error { return uimodels.OperationFn(func(ctx context.Context) error {

View file

@ -14,23 +14,25 @@ import (
type Model struct { type Model struct {
tableReadController *controllers.TableReadController tableReadController *controllers.TableReadController
commandController *commandctrl.CommandController commandController *commandctrl.CommandController
statusAndPrompt *statusandprompt.StatusAndPrompt
tableSelect *tableselect.Model
root tea.Model root tea.Model
} }
func NewModel(rc *controllers.TableReadController, cc *commandctrl.CommandController) Model { func NewModel(rc *controllers.TableReadController, cc *commandctrl.CommandController) Model {
dtv := dynamotableview.New(rc, cc) dtv := dynamotableview.New()
div := dynamoitemview.New() div := dynamoitemview.New()
statusAndPrompt := statusandprompt.New(layout.NewVBox(layout.LastChildFixedAt(17), dtv, div), "")
tableSelect := tableselect.New(statusAndPrompt)
m := statusandprompt.New( root := layout.FullScreen(tableSelect)
layout.NewVBox(layout.LastChildFixedAt(17), dtv, div),
"Hello world",
)
root := layout.FullScreen(tableselect.New(m))
return Model{ return Model{
tableReadController: rc, tableReadController: rc,
commandController: cc, commandController: cc,
statusAndPrompt: statusAndPrompt,
tableSelect: tableSelect,
root: root, root: root,
} }
} }
@ -40,6 +42,20 @@ func (m Model) Init() tea.Cmd {
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
if !m.statusAndPrompt.InPrompt() && !m.tableSelect.Visible() {
switch msg.String() {
case "s":
return m, m.tableReadController.Rescan()
case ":":
return m, m.commandController.Prompt()
case "ctrl+c", "esc":
return m, tea.Quit
}
}
}
var cmd tea.Cmd var cmd tea.Cmd
m.root, cmd = m.root.Update(msg) m.root, cmd = m.root.Update(msg)
return m, cmd return m, cmd

View file

@ -25,18 +25,18 @@ type Model struct {
selectedItem models.Item selectedItem models.Item
} }
func New() Model { func New() *Model {
return Model{ return &Model{
frameTitle: frame.NewFrameTitle("Item", false), frameTitle: frame.NewFrameTitle("Item", false),
viewport: viewport.New(100, 100), viewport: viewport.New(100, 100),
} }
} }
func (Model) Init() tea.Cmd { func (*Model) Init() tea.Cmd {
return nil return nil
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case NewItemSelected: case NewItemSelected:
m.currentResultSet = msg.ResultSet m.currentResultSet = msg.ResultSet
@ -47,14 +47,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
func (m Model) View() string { func (m *Model) View() string {
if !m.ready { if !m.ready {
return "" return ""
} }
return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.viewport.View()) return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.viewport.View())
} }
func (m Model) Resize(w, h int) layout.ResizingModel { func (m *Model) Resize(w, h int) layout.ResizingModel {
m.w, m.h = w, h m.w, m.h = w, h
if !m.ready { if !m.ready {
m.viewport = viewport.New(w, h-m.frameTitle.HeaderHeight()) m.viewport = viewport.New(w, h-m.frameTitle.HeaderHeight())

View file

@ -4,7 +4,6 @@ import (
table "github.com/calyptia/go-bubble-table" table "github.com/calyptia/go-bubble-table"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/lmika/awstools/internal/common/ui/commandctrl"
"github.com/lmika/awstools/internal/dynamo-browse/controllers" "github.com/lmika/awstools/internal/dynamo-browse/controllers"
"github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/models"
"github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamoitemview" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/dynamoitemview"
@ -13,9 +12,6 @@ import (
) )
type Model struct { type Model struct {
tableReadControllers *controllers.TableReadController
commandCtrl *commandctrl.CommandController
frameTitle frame.FrameTitle frameTitle frame.FrameTitle
table table.Model table table.Model
w, h int w, h int
@ -24,26 +20,24 @@ type Model struct {
resultSet *models.ResultSet resultSet *models.ResultSet
} }
func New(tableReadControllers *controllers.TableReadController, commandCtrl *commandctrl.CommandController) Model { func New() *Model {
tbl := table.New([]string{"pk", "sk"}, 100, 100) tbl := table.New([]string{"pk", "sk"}, 100, 100)
rows := make([]table.Row, 0) rows := make([]table.Row, 0)
tbl.SetRows(rows) tbl.SetRows(rows)
frameTitle := frame.NewFrameTitle("No table", true) frameTitle := frame.NewFrameTitle("No table", true)
return Model{ return &Model{
tableReadControllers: tableReadControllers,
commandCtrl: commandCtrl,
frameTitle: frameTitle, frameTitle: frameTitle,
table: tbl, table: tbl,
} }
} }
func (m Model) Init() tea.Cmd { func (m *Model) Init() tea.Cmd {
return nil return nil
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case controllers.NewResultSet: case controllers.NewResultSet:
m.resultSet = msg.ResultSet m.resultSet = msg.ResultSet
@ -58,26 +52,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "k", "down": case "k", "down":
m.table.GoDown() m.table.GoDown()
return m, m.postSelectedItemChanged return m, m.postSelectedItemChanged
// TEMP
case "s":
return m, m.tableReadControllers.Rescan(m.resultSet)
case ":":
return m, m.commandCtrl.Prompt()
// END TEMP
case "ctrl+c", "esc":
return m, tea.Quit
} }
} }
return m, nil return m, nil
} }
func (m Model) View() string { func (m *Model) View() string {
return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View()) return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.table.View())
} }
func (m Model) Resize(w, h int) layout.ResizingModel { func (m *Model) Resize(w, h int) layout.ResizingModel {
m.w, m.h = w, h m.w, m.h = w, h
tblHeight := h - m.frameTitle.HeaderHeight() tblHeight := h - m.frameTitle.HeaderHeight()
m.table.SetSize(w, tblHeight) m.table.SetSize(w, tblHeight)
@ -120,31 +105,3 @@ func (m *Model) postSelectedItemChanged() tea.Msg {
return dynamoitemview.NewItemSelected{ResultSet: item.resultSet, Item: item.item} return dynamoitemview.NewItemSelected{ResultSet: item.resultSet, Item: item.item}
} }
/*
func (m *Model) updateViewportToSelectedMessage() {
selectedItem, ok := m.selectedItem()
if !ok {
m.viewport.SetContent("(no row selected)")
return
}
viewportContent := &strings.Builder{}
tabWriter := tabwriter.NewWriter(viewportContent, 0, 1, 1, ' ', 0)
for _, colName := range selectedItem.resultSet.Columns {
switch colVal := selectedItem.item[colName].(type) {
case nil:
break
case *types.AttributeValueMemberS:
fmt.Fprintf(tabWriter, "%v\tS\t%s\n", colName, colVal.Value)
case *types.AttributeValueMemberN:
fmt.Fprintf(tabWriter, "%v\tN\t%s\n", colName, colVal.Value)
default:
fmt.Fprintf(tabWriter, "%v\t?\t%s\n", colName, "(other)")
}
}
tabWriter.Flush()
m.viewport.SetContent(viewportContent.String())
}
*/

View file

@ -19,16 +19,16 @@ type Model struct {
w, h int w, h int
} }
func New(submodel tea.Model) Model { func New(submodel tea.Model) *Model {
frameTitle := frame.NewFrameTitle("Select table", false) frameTitle := frame.NewFrameTitle("Select table", false)
return Model{frameTitle: frameTitle, submodel: submodel} return &Model{frameTitle: frameTitle, submodel: submodel}
} }
func (m Model) Init() tea.Cmd { func (m *Model) Init() tea.Cmd {
return m.submodel.Init() return m.submodel.Init()
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cc utils.CmdCollector var cc utils.CmdCollector
switch msg := msg.(type) { switch msg := msg.(type) {
case controllers.PromptForTableMsg: case controllers.PromptForTableMsg:
@ -60,7 +60,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cc.Cmd() return m, cc.Cmd()
} }
func (m Model) View() string { func (m *Model) View() string {
if m.pendingSelection != nil { if m.pendingSelection != nil {
return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.listController.View()) return lipgloss.JoinVertical(lipgloss.Top, m.frameTitle.View(), m.listController.View())
} else if m.isLoading { } else if m.isLoading {
@ -70,11 +70,11 @@ func (m Model) View() string {
return m.submodel.View() return m.submodel.View()
} }
func (m Model) shouldShow() bool { func (m *Model) Visible() bool {
return m.pendingSelection != nil || m.isLoading return m.pendingSelection != nil || m.isLoading
} }
func (m Model) Resize(w, h int) layout.ResizingModel { func (m *Model) Resize(w, h int) layout.ResizingModel {
m.w, m.h = w, h m.w, m.h = w, h
m.submodel = layout.Resize(m.submodel, w, h) m.submodel = layout.Resize(m.submodel, w, h)