315 lines
8.0 KiB
Go
315 lines
8.0 KiB
Go
package view
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/derailed/k9s/internal"
|
|
"github.com/derailed/k9s/internal/client"
|
|
"github.com/derailed/k9s/internal/config"
|
|
"github.com/derailed/k9s/internal/perf"
|
|
"github.com/derailed/k9s/internal/render"
|
|
"github.com/derailed/k9s/internal/ui"
|
|
"github.com/derailed/tview"
|
|
"github.com/fsnotify/fsnotify"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const (
|
|
portForwardTitle = "PortForwards"
|
|
promptPage = "prompt"
|
|
)
|
|
|
|
// PortForward presents active portforward viewer.
|
|
type PortForward struct {
|
|
ResourceViewer
|
|
|
|
bench *perf.Benchmark
|
|
}
|
|
|
|
// NewPortForward returns a new viewer.
|
|
func NewPortForward(gvr client.GVR) ResourceViewer {
|
|
p := PortForward{
|
|
ResourceViewer: NewBrowser(gvr),
|
|
}
|
|
p.GetTable().SetBorderFocusColor(tcell.ColorDodgerBlue)
|
|
p.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorDodgerBlue, tcell.AttrNone)
|
|
p.GetTable().SetColorerFn(render.PortForward{}.ColorerFunc())
|
|
p.GetTable().SetSortCol(p.GetTable().NameColIndex()+6, 0, true)
|
|
p.SetContextFn(p.portForwardContext)
|
|
p.SetBindKeysFn(p.bindKeys)
|
|
|
|
return &p
|
|
}
|
|
|
|
func (p *PortForward) portForwardContext(ctx context.Context) context.Context {
|
|
return context.WithValue(ctx, internal.KeyBenchCfg, p.App().Bench)
|
|
}
|
|
|
|
// BOZO!!
|
|
// // Start runs the refresh loop.
|
|
// func (p *PortForward) Start() {
|
|
// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster)
|
|
// var ctx context.Context
|
|
// ctx, p.cancelFn = context.WithCancel(context.Background())
|
|
// if err := watchFS(ctx, p.App(), config.K9sHome, path, p.reload); err != nil {
|
|
// p.App().Flash().Errf("RuRoh! Unable to watch benchmarks directory %s : %s", config.K9sHome, err)
|
|
// }
|
|
// }
|
|
|
|
// // Name returns the component name.
|
|
// func (p *PortForward) Name() string {
|
|
// return portForwardTitle
|
|
// }
|
|
|
|
// func (p *PortForward) reload() {
|
|
// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster)
|
|
// log.Debug().Msgf("Reloading Config %s", path)
|
|
// if err := p.App().Bench.Reload(path); err != nil {
|
|
// p.App().Flash().Err(err)
|
|
// }
|
|
// p.refresh()
|
|
// }
|
|
|
|
// func (p *PortForward) refresh() {
|
|
// p.Update(p.hydrate())
|
|
// p.App().SetFocus(p)
|
|
// p.UpdateTitle()
|
|
// }
|
|
|
|
func (p *PortForward) bindKeys(aa ui.KeyActions) {
|
|
aa.Add(ui.KeyActions{
|
|
tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.showBenchCmd, true),
|
|
tcell.KeyCtrlB: ui.NewKeyAction("Bench", p.benchCmd, true),
|
|
tcell.KeyCtrlK: ui.NewKeyAction("Bench Stop", p.benchStopCmd, true),
|
|
tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true),
|
|
// ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false),
|
|
tcell.KeyEsc: ui.NewKeyAction("Back", p.App().PrevCmd, false),
|
|
ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.GetTable().SortColCmd(2, true), false),
|
|
ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.GetTable().SortColCmd(4, true), false),
|
|
})
|
|
}
|
|
|
|
func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
p.App().inject(NewBenchmark("benchmarks"))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PortForward) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if p.bench != nil {
|
|
log.Debug().Msg(">>> Benchmark cancelFned!!")
|
|
p.App().status(ui.FlashErr, "Benchmark Camceled!")
|
|
p.bench.Cancel()
|
|
}
|
|
p.App().StatusReset()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
sel := p.getSelectedItem()
|
|
if sel == "" {
|
|
return nil
|
|
}
|
|
|
|
if p.bench != nil {
|
|
p.App().Flash().Err(errors.New("Only one benchmark allowed at a time"))
|
|
return nil
|
|
}
|
|
|
|
r, _ := p.GetTable().GetSelection()
|
|
cfg, co := defaultConfig(), ui.TrimCell(p.GetTable().SelectTable, r, 2)
|
|
if b, ok := p.App().Bench.Benchmarks.Containers[containerID(sel, co)]; ok {
|
|
cfg = b
|
|
}
|
|
cfg.Name = sel
|
|
|
|
base := ui.TrimCell(p.GetTable().SelectTable, r, 4)
|
|
var err error
|
|
if p.bench, err = perf.NewBenchmark(base, cfg); err != nil {
|
|
p.App().Flash().Errf("Bench failed %v", err)
|
|
p.App().StatusReset()
|
|
return nil
|
|
}
|
|
|
|
p.App().status(ui.FlashWarn, "Benchmark in progress...")
|
|
log.Debug().Msg("Bench starting...")
|
|
go p.runBenchmark()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *PortForward) runBenchmark() {
|
|
p.bench.Run(p.App().Config.K9s.CurrentCluster, func() {
|
|
log.Debug().Msg("Bench Completed!")
|
|
p.App().QueueUpdate(func() {
|
|
if p.bench.Canceled() {
|
|
p.App().status(ui.FlashInfo, "Benchmark cancelFned")
|
|
} else {
|
|
p.App().status(ui.FlashInfo, "Benchmark Completed!")
|
|
p.bench.Cancel()
|
|
}
|
|
p.bench = nil
|
|
go func() {
|
|
<-time.After(2 * time.Second)
|
|
p.App().QueueUpdate(func() { p.App().StatusReset() })
|
|
}()
|
|
})
|
|
})
|
|
}
|
|
|
|
func (p *PortForward) getSelectedItem() string {
|
|
r, _ := p.GetTable().GetSelection()
|
|
if r == 0 {
|
|
return ""
|
|
}
|
|
return fwFQN(
|
|
fqn(ui.TrimCell(p.GetTable().SelectTable, r, 0), ui.TrimCell(p.GetTable().SelectTable, r, 1)),
|
|
ui.TrimCell(p.GetTable().SelectTable, r, 2),
|
|
)
|
|
}
|
|
|
|
func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !p.GetTable().SearchBuff().Empty() {
|
|
p.GetTable().SearchBuff().Reset()
|
|
return nil
|
|
}
|
|
|
|
sel := p.getSelectedItem()
|
|
if sel == "" {
|
|
return nil
|
|
}
|
|
|
|
showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() {
|
|
p.App().factory.DeleteForwarder(sel)
|
|
p.App().Flash().Infof("PortForward %s deleted!", sel)
|
|
p.GetTable().Refresh()
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// func (p *PortForward) hydrate() render.TableData {
|
|
// var re render.Forward
|
|
|
|
// data := render.TableData{
|
|
// Header: re.Header(render.AllNamespaces),
|
|
// RowEvents: make(render.RowEvents, 0, len(p.App().forwarders)),
|
|
// Namespace: render.AllNamespaces,
|
|
// }
|
|
|
|
// containers := p.App().Bench.Benchmarks.Containers
|
|
// for _, f := range p.App().forwarders {
|
|
// fqn := containerID(f.Path(), f.Container())
|
|
// cfg := benchCfg{
|
|
// c: p.App().Bench.Benchmarks.Defaults.C,
|
|
// n: p.App().Bench.Benchmarks.Defaults.N,
|
|
// }
|
|
// if config, ok := containers[fqn]; ok {
|
|
// cfg.c, cfg.n = config.C, config.N
|
|
// cfg.host, cfg.path = config.HTTP.Host, config.HTTP.Path
|
|
// }
|
|
|
|
// var row render.Row
|
|
// fwd := forwarder{
|
|
// Forwarder: f,
|
|
// BenchConfigurator: cfg,
|
|
// }
|
|
// if err := re.Render(fwd, render.AllNamespaces, &row); err != nil {
|
|
// log.Error().Err(err).Msgf("PortForward render failed")
|
|
// continue
|
|
// }
|
|
// data.RowEvents = append(data.RowEvents, render.RowEvent{Kind: render.EventAdd, Row: row})
|
|
// }
|
|
|
|
// return data
|
|
// }
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Helpers...
|
|
|
|
// var _ render.PortForwarder = forwarder{}
|
|
|
|
// type forwarder struct {
|
|
// render.Forwarder
|
|
// render.BenchConfigurator
|
|
// }
|
|
|
|
// type benchCfg struct {
|
|
// c, n int
|
|
// host, path string
|
|
// }
|
|
|
|
// func (b benchCfg) C() int { return b.c }
|
|
// func (b benchCfg) N() int { return b.n }
|
|
// func (b benchCfg) Host() string { return b.host }
|
|
// func (b benchCfg) HttpPath() string { return b.path }
|
|
|
|
func defaultConfig() config.BenchConfig {
|
|
return config.BenchConfig{
|
|
C: config.DefaultC,
|
|
N: config.DefaultN,
|
|
HTTP: config.HTTP{
|
|
Method: config.DefaultMethod,
|
|
Path: "/",
|
|
},
|
|
}
|
|
}
|
|
|
|
func showModal(p *ui.Pages, msg string, ok func()) {
|
|
m := tview.NewModal().
|
|
AddButtons([]string{"Cancel", "OK"}).
|
|
SetTextColor(tcell.ColorFuchsia).
|
|
SetText(msg).
|
|
SetDoneFunc(func(_ int, b string) {
|
|
if b == "OK" {
|
|
ok()
|
|
}
|
|
dismissModal(p)
|
|
})
|
|
m.SetTitle("<Delete Benchmark>")
|
|
p.AddPage(promptPage, m, false, false)
|
|
p.ShowPage(promptPage)
|
|
}
|
|
|
|
func dismissModal(p *ui.Pages) {
|
|
p.RemovePage(promptPage)
|
|
}
|
|
|
|
func watchFS(ctx context.Context, app *App, dir, file string, cb func()) error {
|
|
w, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
go func() {
|
|
for {
|
|
select {
|
|
case evt := <-w.Events:
|
|
log.Debug().Msgf("FS %s event %v", file, evt.Name)
|
|
if file == "" || evt.Name == file {
|
|
log.Debug().Msgf("Capturing Event %#v", evt)
|
|
app.QueueUpdateDraw(func() {
|
|
cb()
|
|
})
|
|
}
|
|
case err := <-w.Errors:
|
|
log.Info().Err(err).Msgf("FS %s watcher failed", dir)
|
|
return
|
|
case <-ctx.Done():
|
|
log.Debug().Msgf("<<FS %s WATCHER DONE>>", dir)
|
|
if err := w.Close(); err != nil {
|
|
log.Error().Err(err).Msg("Closing portforward watcher")
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return w.Add(dir)
|
|
}
|