Add details pane (#23)
This commit is contained in:
parent
0ec279788e
commit
85fa13501d
@ -135,8 +135,8 @@ func InitializeData(imageID string) ([]*Layer, []*filetree.FileTree) {
|
||||
// save this image to disk temporarily to get the content info
|
||||
fmt.Println("Fetching image...")
|
||||
imageTarPath, tmpDir := saveImage(imageID)
|
||||
// imageTarPath := "/tmp/dive932744808/image.tar"
|
||||
// tmpDir := "/tmp/dive031537738"
|
||||
// imageTarPath := "/tmp/dive229500681/image.tar"
|
||||
// tmpDir := "/tmp/dive229500681"
|
||||
// fmt.Println(tmpDir)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
|
95
ui/detailsview.go
Normal file
95
ui/detailsview.go
Normal file
@ -0,0 +1,95 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jroimartin/gocui"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DetailsView struct {
|
||||
Name string
|
||||
gui *gocui.Gui
|
||||
view *gocui.View
|
||||
header *gocui.View
|
||||
}
|
||||
|
||||
func NewStatisticsView(name string, gui *gocui.Gui) (detailsview *DetailsView) {
|
||||
detailsview = new(DetailsView)
|
||||
|
||||
// populate main fields
|
||||
detailsview.Name = name
|
||||
detailsview.gui = gui
|
||||
|
||||
return detailsview
|
||||
}
|
||||
|
||||
func (view *DetailsView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
|
||||
// set view options
|
||||
view.view = v
|
||||
view.view.Editable = false
|
||||
view.view.Wrap = true
|
||||
view.view.Highlight = false
|
||||
view.view.Frame = false
|
||||
|
||||
view.header = header
|
||||
view.header.Editable = false
|
||||
view.header.Wrap = false
|
||||
view.header.Frame = false
|
||||
|
||||
// set keybindings
|
||||
if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowDown, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorDown() }); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := view.gui.SetKeybinding(view.Name, gocui.KeyArrowUp, gocui.ModNone, func(*gocui.Gui, *gocui.View) error { return view.CursorUp() }); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return view.Render()
|
||||
}
|
||||
|
||||
func (view *DetailsView) IsVisible() bool {
|
||||
if view == nil {return false}
|
||||
return true
|
||||
}
|
||||
|
||||
func (view *DetailsView) Update() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (view *DetailsView) Render() error {
|
||||
currentLayer := Views.Layer.currentLayer()
|
||||
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
// update header
|
||||
view.header.Clear()
|
||||
width, _ := g.Size()
|
||||
headerStr := fmt.Sprintf("[Image & Layer Details]%s", strings.Repeat("─",width*2))
|
||||
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
|
||||
|
||||
// update contents
|
||||
view.view.Clear()
|
||||
fmt.Fprintln(view.view, Formatting.Header("Command"))
|
||||
fmt.Fprintln(view.view, currentLayer.History.CreatedBy)
|
||||
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (view *DetailsView) CursorDown() error {
|
||||
return CursorDown(view.gui, view.view)
|
||||
}
|
||||
|
||||
func (view *DetailsView) CursorUp() error {
|
||||
return CursorUp(view.gui, view.view)
|
||||
}
|
||||
|
||||
|
||||
func (view *DetailsView) KeyHelp() string {
|
||||
return "TBD"
|
||||
// return renderStatusOption("^L","Layer changes", view.CompareMode == CompareLayer) +
|
||||
// renderStatusOption("^A","All changes", view.CompareMode == CompareAll)
|
||||
}
|
@ -101,6 +101,13 @@ func (view *FileTreeView) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (view *FileTreeView) resetCursor() {
|
||||
view.view.SetCursor(0, 0)
|
||||
view.TreeIndex = 0
|
||||
view.bufferIndex = 0
|
||||
view.bufferIndexLowerBound = 0
|
||||
view.bufferIndexUpperBound = view.height()
|
||||
}
|
||||
|
||||
func (view *FileTreeView) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) error {
|
||||
if topTreeStop > len(view.RefTrees)-1 {
|
||||
@ -122,8 +129,8 @@ func (view *FileTreeView) setTreeByLayer(bottomTreeStart, bottomTreeStop, topTre
|
||||
}
|
||||
view.ModelTree.VisitDepthChildFirst(visitor, nil)
|
||||
|
||||
view.view.SetCursor(0, 0)
|
||||
view.TreeIndex = 0
|
||||
view.resetCursor()
|
||||
|
||||
view.ModelTree = newTree
|
||||
view.Update()
|
||||
return view.Render()
|
||||
@ -227,8 +234,7 @@ func (view *FileTreeView) toggleCollapse() error {
|
||||
func (view *FileTreeView) toggleShowDiffType(diffType filetree.DiffType) error {
|
||||
view.HiddenDiffTypes[diffType] = !view.HiddenDiffTypes[diffType]
|
||||
|
||||
view.view.SetCursor(0, 0)
|
||||
view.TreeIndex = 0
|
||||
view.resetCursor()
|
||||
|
||||
Update()
|
||||
Render()
|
||||
@ -299,10 +305,22 @@ func (view *FileTreeView) Render() error {
|
||||
view.doCursorUp()
|
||||
}
|
||||
|
||||
title := "Current Layer Contents"
|
||||
if Views.Layer.CompareMode == CompareAll {
|
||||
title = "Aggregated Layer Contents"
|
||||
}
|
||||
|
||||
// indicate when selected
|
||||
if view.gui.CurrentView() == view.view {
|
||||
title = "● "+title
|
||||
}
|
||||
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
// update the header
|
||||
view.header.Clear()
|
||||
headerStr := fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
|
||||
width, _ := g.Size()
|
||||
headerStr := fmt.Sprintf("[%s]%s\n", title, strings.Repeat("─", width*2))
|
||||
headerStr += fmt.Sprintf(filetree.AttributeFormat+" %s", "P", "ermission", "UID:GID", "Size", "Filetree")
|
||||
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
|
||||
|
||||
// update the contents
|
||||
|
@ -18,13 +18,6 @@ type FilterView struct {
|
||||
hidden bool
|
||||
}
|
||||
|
||||
type Input struct {
|
||||
name string
|
||||
x, y int
|
||||
w int
|
||||
maxLength int
|
||||
}
|
||||
|
||||
func NewFilterView(name string, gui *gocui.Gui) (filterview *FilterView) {
|
||||
filterview = new(FilterView)
|
||||
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/wagoodman/dive/image"
|
||||
"github.com/lunixbochs/vtclean"
|
||||
"github.com/dustin/go-humanize"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type LayerView struct {
|
||||
@ -38,9 +39,6 @@ func (view *LayerView) Setup(v *gocui.View, header *gocui.View) error {
|
||||
view.view = v
|
||||
view.view.Editable = false
|
||||
view.view.Wrap = false
|
||||
//view.view.Highlight = true
|
||||
//view.view.SelBgColor = gocui.ColorGreen
|
||||
//view.view.SelFgColor = gocui.ColorBlack
|
||||
view.view.Frame = false
|
||||
|
||||
view.header = header
|
||||
@ -70,6 +68,10 @@ func (view *LayerView) IsVisible() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (view *LayerView) currentLayer() *image.Layer {
|
||||
return view.Layers[(len(view.Layers)-1)-view.LayerIndex]
|
||||
}
|
||||
|
||||
func (view *LayerView) setCompareMode(compareMode CompareType) error {
|
||||
view.CompareMode = compareMode
|
||||
Update()
|
||||
@ -99,12 +101,6 @@ func (view *LayerView) renderCompareBar(layerIdx int) string {
|
||||
bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop := view.getCompareIndexes()
|
||||
result := " "
|
||||
|
||||
//if debug {
|
||||
// v, _ := view.gui.View("debug")
|
||||
// v.Clear()
|
||||
// _, _ = fmt.Fprintf(v, "bStart: %d bStop: %d tStart: %d tStop: %d", bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
|
||||
//}
|
||||
|
||||
if layerIdx >= bottomTreeStart && layerIdx <= bottomTreeStop {
|
||||
result = Formatting.CompareBottom(" ")
|
||||
}
|
||||
@ -112,18 +108,6 @@ func (view *LayerView) renderCompareBar(layerIdx int) string {
|
||||
result = Formatting.CompareTop(" ")
|
||||
}
|
||||
|
||||
//if bottomTreeStop == topTreeStart {
|
||||
// result += " "
|
||||
//} else {
|
||||
// if layerIdx == bottomTreeStop {
|
||||
// result += "─┐"
|
||||
// } else if layerIdx == topTreeStart {
|
||||
// result += "─┘"
|
||||
// } else {
|
||||
// result += " "
|
||||
// }
|
||||
//}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@ -132,9 +116,19 @@ func (view *LayerView) Update() error {
|
||||
}
|
||||
|
||||
func (view *LayerView) Render() error {
|
||||
|
||||
// indicate when selected
|
||||
title := "Layers"
|
||||
if view.gui.CurrentView() == view.view {
|
||||
title = "● "+title
|
||||
}
|
||||
|
||||
view.gui.Update(func(g *gocui.Gui) error {
|
||||
// update header
|
||||
headerStr := fmt.Sprintf("Cmp "+image.LayerFormat, "Image ID", "%Eff.", "Size", "Filter")
|
||||
view.header.Clear()
|
||||
width, _ := g.Size()
|
||||
headerStr := fmt.Sprintf("[%s]%s\n", title, strings.Repeat("─", width*2))
|
||||
headerStr += fmt.Sprintf("Cmp "+image.LayerFormat, "Image ID", "%Eff.", "Size", "Command")
|
||||
fmt.Fprintln(view.header, Formatting.Header(vtclean.Clean(headerStr, false)))
|
||||
|
||||
// update contents
|
||||
@ -174,10 +168,7 @@ func (view *LayerView) CursorDown() error {
|
||||
if view.LayerIndex < len(view.Layers) {
|
||||
err := CursorDown(view.gui, view.view)
|
||||
if err == nil {
|
||||
view.LayerIndex++
|
||||
Views.Tree.setTreeByLayer(view.getCompareIndexes())
|
||||
view.Render()
|
||||
// debugPrint(fmt.Sprintf("%d",len(filetree.Cache)))
|
||||
view.SetCursor(view.LayerIndex+1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -187,25 +178,22 @@ func (view *LayerView) CursorUp() error {
|
||||
if view.LayerIndex > 0 {
|
||||
err := CursorUp(view.gui, view.view)
|
||||
if err == nil {
|
||||
view.LayerIndex--
|
||||
Views.Tree.setTreeByLayer(view.getCompareIndexes())
|
||||
view.Render()
|
||||
// debugPrint(fmt.Sprintf("%d",len(filetree.Cache)))
|
||||
view.SetCursor(view.LayerIndex-1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (view *LayerView) SetCursor(layer int) error {
|
||||
// view.view.SetCursor(0, layer)
|
||||
view.LayerIndex = layer
|
||||
Views.Tree.setTreeByLayer(view.getCompareIndexes())
|
||||
Views.Details.Render()
|
||||
view.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (view *LayerView) KeyHelp() string {
|
||||
return renderStatusOption("^L","Layer changes", view.CompareMode == CompareLayer) +
|
||||
renderStatusOption("^A","All changes", view.CompareMode == CompareAll)
|
||||
return renderStatusOption("^L","Show layer changes", view.CompareMode == CompareLayer) +
|
||||
renderStatusOption("^A","Show aggregated changes", view.CompareMode == CompareAll)
|
||||
}
|
||||
|
30
ui/ui.go
30
ui/ui.go
@ -41,11 +41,12 @@ var Formatting struct {
|
||||
}
|
||||
|
||||
var Views struct {
|
||||
Tree *FileTreeView
|
||||
Layer *LayerView
|
||||
Status *StatusView
|
||||
Filter *FilterView
|
||||
lookup map[string]View
|
||||
Tree *FileTreeView
|
||||
Layer *LayerView
|
||||
Status *StatusView
|
||||
Filter *FilterView
|
||||
Details *DetailsView
|
||||
lookup map[string]View
|
||||
}
|
||||
|
||||
type View interface {
|
||||
@ -178,7 +179,7 @@ func layout(g *gocui.Gui) error {
|
||||
}
|
||||
debugCols := maxX - debugWidth
|
||||
bottomRows := 1
|
||||
headerRows := 1
|
||||
headerRows := 2
|
||||
|
||||
filterBarHeight := 1
|
||||
statusBarHeight := 1
|
||||
@ -186,6 +187,8 @@ func layout(g *gocui.Gui) error {
|
||||
statusBarIndex := 1
|
||||
filterBarIndex := 2
|
||||
|
||||
layersHeight := len(Views.Layer.Layers) + headerRows + 1 // layers + header + base image layer row
|
||||
|
||||
var view, header *gocui.View
|
||||
var viewErr, headerErr, err error
|
||||
|
||||
@ -204,7 +207,7 @@ func layout(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
// Layers
|
||||
view, viewErr = g.SetView(Views.Layer.Name, -1, -1+headerRows, splitCols, maxY-bottomRows)
|
||||
view, viewErr = g.SetView(Views.Layer.Name, -1, -1+headerRows, splitCols, layersHeight)
|
||||
header, headerErr = g.SetView(Views.Layer.Name+"header", -1, -1, splitCols, headerRows)
|
||||
if isNewView(viewErr, headerErr) {
|
||||
Views.Layer.Setup(view, header)
|
||||
@ -212,6 +215,15 @@ func layout(g *gocui.Gui) error {
|
||||
if _, err = g.SetCurrentView(Views.Layer.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
// since we are selecting the view, we should rerender to indicate it is selected
|
||||
Views.Layer.Render()
|
||||
}
|
||||
|
||||
// Details
|
||||
view, viewErr = g.SetView(Views.Details.Name, -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows)
|
||||
header, headerErr = g.SetView(Views.Details.Name+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows)
|
||||
if isNewView(viewErr, headerErr) {
|
||||
Views.Details.Setup(view, header)
|
||||
}
|
||||
|
||||
// Filetree
|
||||
@ -291,6 +303,10 @@ func Run(layers []*image.Layer, refTrees []*filetree.FileTree) {
|
||||
Views.Filter = NewFilterView("command", g)
|
||||
Views.lookup[Views.Filter.Name] = Views.Filter
|
||||
|
||||
Views.Details = NewStatisticsView("details", g)
|
||||
Views.lookup[Views.Details.Name] = Views.Details
|
||||
|
||||
|
||||
g.Cursor = false
|
||||
//g.Mouse = true
|
||||
g.SetManagerFunc(layout)
|
||||
|
Loading…
x
Reference in New Issue
Block a user