package views import ( "context" "errors" "strings" "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/derailed/tview" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" "k8s.io/client-go/tools/portforward" ) type containerView struct { *resourceView current igniter exitFn func() } func newContainerView(t string, app *appView, list resource.List, path string, exitFn func()) resourceViewer { v := containerView{resourceView: newResourceView(t, app, list).(*resourceView)} { v.path = &path v.extraActionsFn = v.extraActions v.colorerFn = containerColorer v.current = app.content.GetPrimitive("main").(igniter) v.exitFn = exitFn } v.AddPage("logs", newLogsView(list.GetName(), &v), true, false) return &v } func (v *containerView) init(ctx context.Context, ns string) { v.resourceView.init(ctx, ns) } func (v *containerView) extraActions(aa keyActions) { aa[KeyL] = newKeyAction("Logs", v.logsCmd, true) aa[KeyShiftF] = newKeyAction("PortForward", v.portFwdCmd, true) aa[KeyShiftL] = newKeyAction("Logs Previous", v.prevLogsCmd, true) aa[KeyS] = newKeyAction("Shell", v.shellCmd, true) aa[tcell.KeyEscape] = newKeyAction("Back", v.backCmd, false) aa[KeyP] = newKeyAction("Previous", v.backCmd, false) aa[tcell.KeyEnter] = newKeyAction("View Logs", v.logsCmd, false) aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(6, false), true) aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(7, false), true) aa[KeyAltC] = newKeyAction("Sort CPU%", v.sortColCmd(8, false), true) aa[KeyAltM] = newKeyAction("Sort MEM%", v.sortColCmd(9, false), true) } // Protocol... func (v *containerView) backFn() actionHandler { return v.backCmd } func (v *containerView) appView() *appView { return v.app } func (v *containerView) getList() resource.List { return v.list } func (v *containerView) getSelection() string { return *v.path } // Handlers... func (v *containerView) logsCmd(evt *tcell.EventKey) *tcell.EventKey { if !v.rowSelected() { return evt } cell := v.getTV().GetCell(v.selectedRow, 3) if cell != nil && strings.TrimSpace(cell.Text) != "Running" { v.app.flash().err(errors.New("No logs for a non running container")) return evt } v.showLogs(v.selectedItem, v.list.GetName(), v, false) return nil } func (v *containerView) prevLogsCmd(evt *tcell.EventKey) *tcell.EventKey { if !v.rowSelected() { return evt } v.showLogs(v.selectedItem, v.list.GetName(), v, true) return nil } func (v *containerView) showLogs(co, view string, parent loggable, prev bool) { l := v.GetPrimitive("logs").(*logsView) l.reload(co, parent, view, prev) v.switchPage("logs") } func (v *containerView) shellCmd(evt *tcell.EventKey) *tcell.EventKey { if !v.rowSelected() { return evt } v.stopUpdates() shellIn(v.app, *v.path, v.selectedItem) v.restartUpdates() return nil } func (v *containerView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey { t := v.getTV() t.sortCol.index, t.sortCol.asc = t.nameColIndex()+col, asc t.refresh() return nil } } func (v *containerView) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { if !v.rowSelected() { return evt } portC := v.getTV().GetCell(v.selectedRow, 10) ports := strings.Split(portC.Text, ",") if len(ports) == 0 { v.app.flash().err(errors.New("Container exposes no ports")) return nil } var port string for _, p := range ports { log.Debug().Msgf("Checking port %q", p) if !isTCPPort(p) { continue } port = strings.TrimSpace(p) break } if port == "" { v.app.flash().err(errors.New("No valid TCP port found on this container")) return nil } f := tview.NewForm() f.SetItemPadding(0) f.SetButtonsAlign(tview.AlignCenter). SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor). SetButtonTextColor(tview.Styles.PrimaryTextColor). SetLabelColor(tcell.ColorAqua). SetFieldTextColor(tcell.ColorOrange) f1, f2 := port, port f.AddInputField("Pod Port:", f1, 20, nil, func(changed string) { f1 = changed }) f.AddInputField("Local Port:", f2, 20, nil, func(changed string) { f2 = changed }) f.AddButton("OK", func() { pf := k8s.NewPortForward(v.app.conn(), &log.Logger) ports := []string{stripPort(f2) + ":" + stripPort(f1)} co := strings.TrimSpace(v.getTV().GetCell(v.selectedRow, 0).Text) fw, err := pf.Start(*v.path, co, ports) if err != nil { log.Error().Err(err).Msg("Fort Forward") v.app.flash().errf("PortForward failed! %v", err) return } log.Debug().Msgf(">>> Starting port forward %q %v", *v.path, ports) go func(f *portforward.PortForwarder) { v.app.QueueUpdateDraw(func() { v.app.forwarders = append(v.app.forwarders, pf) v.app.flash().infof("PortForward activated %s:%s", pf.Path(), pf.Ports()[0]) v.dismissModal() }) pf.SetActive(true) if err := f.ForwardPorts(); err != nil { log.Error().Err(err).Msg("Port forward failed") v.app.QueueUpdateDraw(func() { if len(v.app.forwarders) > 0 { v.app.forwarders = v.app.forwarders[:len(v.app.forwarders)-1] } pf.SetActive(false) v.app.flash().errf("PortForward failed %s", err) }) } }(fw) }) f.AddButton("Cancel", func() { v.app.flash().info("Canceled!!") v.dismissModal() }) modal := tview.NewModalForm("", f) modal.SetDoneFunc(func(_ int, b string) { v.dismissModal() }) v.AddPage("dialog", modal, false, false) v.ShowPage("dialog") return nil } func (v *containerView) dismissModal() { v.RemovePage("dialog") v.switchPage(v.list.GetName()) } func (v *containerView) backCmd(evt *tcell.EventKey) *tcell.EventKey { v.exitFn() return nil }