228 lines
5.2 KiB
Go
228 lines
5.2 KiB
Go
package view
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/derailed/k9s/internal/config"
|
|
"github.com/derailed/k9s/internal/model"
|
|
"github.com/derailed/k9s/internal/resource"
|
|
"github.com/derailed/k9s/internal/ui"
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const (
|
|
dumpTitle = "Screen Dumps"
|
|
dumpTitleFmt = " [mediumvioletred::b]%s([fuchsia::b]%d[fuchsia::-])[mediumvioletred::-] "
|
|
)
|
|
|
|
var (
|
|
dumpHeader = resource.Row{"NAME", "AGE"}
|
|
)
|
|
|
|
// ScreenDump presents a directory listing viewer.
|
|
type ScreenDump struct {
|
|
*MasterDetail
|
|
|
|
cancelFn context.CancelFunc
|
|
app *App
|
|
}
|
|
|
|
func NewScreenDump(_, _ string, _ resource.List) ResourceViewer {
|
|
return &ScreenDump{
|
|
MasterDetail: NewMasterDetail(),
|
|
}
|
|
}
|
|
|
|
// Init initializes the viewer.
|
|
func (s *ScreenDump) Init(ctx context.Context) {
|
|
s.app = ctx.Value(ui.KeyApp).(*App)
|
|
|
|
table := s.masterPage()
|
|
table.SetBorderFocusColor(tcell.ColorSteelBlue)
|
|
table.SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone)
|
|
table.SetColorerFn(dumpColorer)
|
|
table.SetActiveNS(resource.AllNamespaces)
|
|
table.SetSortCol(table.NameColIndex()+1, 0, true)
|
|
table.SelectRow(1, true)
|
|
|
|
s.Start()
|
|
s.refresh()
|
|
}
|
|
|
|
// Start starts the directory watcher.
|
|
func (s *ScreenDump) Start() {
|
|
var ctx context.Context
|
|
ctx, s.cancelFn = context.WithCancel(context.Background())
|
|
if err := s.watchDumpDir(ctx); err != nil {
|
|
s.app.Flash().Errf("Unable to watch dumpmarks directory %s", err)
|
|
}
|
|
}
|
|
|
|
// Stop terminates the directory watcher.
|
|
func (s *ScreenDump) Stop() {
|
|
if s.cancelFn != nil {
|
|
s.cancelFn()
|
|
}
|
|
}
|
|
|
|
// Name returns the component name.
|
|
func (s *ScreenDump) Name() string {
|
|
return dumpTitle
|
|
}
|
|
|
|
func (s *ScreenDump) setEnterFn(enterFn) {}
|
|
func (s *ScreenDump) setColorerFn(ui.ColorerFunc) {}
|
|
func (s *ScreenDump) setDecorateFn(decorateFn) {}
|
|
func (s *ScreenDump) setExtraActionsFn(ActionsFunc) {}
|
|
|
|
func (s *ScreenDump) refresh() {
|
|
tv := s.masterPage()
|
|
tv.Update(s.hydrate())
|
|
tv.UpdateTitle()
|
|
}
|
|
|
|
func (s *ScreenDump) registerActions() {
|
|
aa := ui.KeyActions{
|
|
ui.KeyP: ui.NewKeyAction("Previous", s.app.PrevCmd, false),
|
|
tcell.KeyEnter: ui.NewKeyAction("Enter", s.enterCmd, true),
|
|
tcell.KeyCtrlD: ui.NewKeyAction("Delete", s.deleteCmd, true),
|
|
tcell.KeyCtrlS: ui.NewKeyAction("Save", noopCmd, false),
|
|
}
|
|
s.masterPage().AddActions(aa)
|
|
}
|
|
|
|
func (s *ScreenDump) getTitle() string {
|
|
return dumpTitle
|
|
}
|
|
|
|
func (s *ScreenDump) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
|
return func(evt *tcell.EventKey) *tcell.EventKey {
|
|
tv := s.masterPage()
|
|
tv.SetSortCol(tv.NameColIndex()+col, 0, asc)
|
|
tv.Refresh()
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (s *ScreenDump) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
log.Debug().Msg("Dump enter!")
|
|
tv := s.masterPage()
|
|
if tv.SearchBuff().IsActive() {
|
|
return tv.filterCmd(evt)
|
|
}
|
|
sel := tv.GetSelectedItem()
|
|
if sel == "" {
|
|
return nil
|
|
}
|
|
|
|
dir := filepath.Join(config.K9sDumpDir, s.app.Config.K9s.CurrentCluster)
|
|
if !edit(true, s.app, filepath.Join(dir, sel)) {
|
|
s.app.Flash().Err(errors.New("Failed to launch editor"))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ScreenDump) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
sel := s.masterPage().GetSelectedItem()
|
|
if sel == "" {
|
|
return nil
|
|
}
|
|
|
|
dir := filepath.Join(config.K9sDumpDir, s.app.Config.K9s.CurrentCluster)
|
|
showModal(s.Pages, fmt.Sprintf("Delete screen dump `%s?", sel), "table", func() {
|
|
if err := os.Remove(filepath.Join(dir, sel)); err != nil {
|
|
s.app.Flash().Errf("Unable to delete file %s", err)
|
|
return
|
|
}
|
|
s.refresh()
|
|
s.app.Flash().Infof("ScreenDump file %s deleted!", sel)
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ScreenDump) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if s.cancelFn != nil {
|
|
s.cancelFn()
|
|
}
|
|
s.SwitchToPage("table")
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *ScreenDump) Hints() model.MenuHints {
|
|
return s.Hints()
|
|
}
|
|
|
|
func (s *ScreenDump) hydrate() resource.TableData {
|
|
data := resource.TableData{
|
|
Header: dumpHeader,
|
|
Rows: make(resource.RowEvents, 10),
|
|
Namespace: resource.NotNamespaced,
|
|
}
|
|
|
|
dir := filepath.Join(config.K9sDumpDir, s.app.Config.K9s.CurrentCluster)
|
|
ff, err := ioutil.ReadDir(dir)
|
|
if err != nil {
|
|
s.app.Flash().Errf("Unable to read dump directory %s", err)
|
|
}
|
|
|
|
for _, f := range ff {
|
|
fields := resource.Row{f.Name(), time.Since(f.ModTime()).String()}
|
|
data.Rows[f.Name()] = &resource.RowEvent{
|
|
Action: resource.New,
|
|
Fields: fields,
|
|
Deltas: fields,
|
|
}
|
|
}
|
|
|
|
return data
|
|
}
|
|
|
|
func (s *ScreenDump) resetTitle() {
|
|
s.SetTitle(fmt.Sprintf(dumpTitleFmt, dumpTitle, s.masterPage().GetRowCount()-1))
|
|
}
|
|
|
|
func (s *ScreenDump) watchDumpDir(ctx context.Context) error {
|
|
w, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case evt := <-w.Events:
|
|
log.Debug().Msgf("Dump event %#v", evt)
|
|
s.app.QueueUpdateDraw(func() {
|
|
s.refresh()
|
|
})
|
|
case err := <-w.Errors:
|
|
log.Info().Err(err).Msg("Dir Watcher failed")
|
|
return
|
|
case <-ctx.Done():
|
|
log.Debug().Msg("!!!! FS WATCHER DONE!!")
|
|
w.Close()
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return w.Add(filepath.Join(config.K9sDumpDir, s.app.Config.K9s.CurrentCluster))
|
|
}
|
|
|
|
// Helpers...
|
|
|
|
func noopCmd(*tcell.EventKey) *tcell.EventKey {
|
|
return nil
|
|
}
|