k9s/internal/view/pf_dialog.go

136 lines
3.7 KiB
Go

package view
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/derailed/k9s/internal/port"
"github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"
)
const portForwardKey = "portforward"
// PortForwardCB represents a port-forward callback function.
type PortForwardCB func(ResourceViewer, string, port.PortTunnels) error
// ShowPortForwards pops a port forwarding configuration dialog.
func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpecs, aa port.Annotations, okFn PortForwardCB) {
styles := v.App().Styles.Dialog()
f := tview.NewForm()
f.SetItemPadding(0)
f.SetButtonsAlign(tview.AlignCenter).
SetButtonBackgroundColor(styles.ButtonBgColor.Color()).
SetButtonTextColor(styles.ButtonFgColor.Color()).
SetLabelColor(styles.LabelFgColor.Color()).
SetFieldTextColor(styles.FieldFgColor.Color()).
SetFieldBackgroundColor(styles.BgColor.Color())
address := v.App().Config.CurrentCluster().PortForwardAddress
pf, err := aa.PreferredPorts(ports)
if err != nil {
log.Warn().Err(err).Msgf("unable to resolve ports on %s", path)
}
p1, p2 := pf.ToPortSpec(ports)
fieldLen := int(math.Max(30, float64(len(p1))))
f.AddInputField("Container Port:", p1, fieldLen, nil, nil)
f.AddInputField("Local Port:", p2, fieldLen, nil, nil)
coField := f.GetFormItemByLabel("Container Port:").(*tview.InputField)
loField := f.GetFormItemByLabel("Local Port:").(*tview.InputField)
if coField.GetText() == "" {
coField.SetPlaceholder("Enter a container name::port")
}
coField.SetChangedFunc(func(s string) {
port := extractPort(s)
loField.SetText(port)
p2 = port
})
if loField.GetText() == "" {
loField.SetPlaceholder("Enter a local port")
}
f.AddInputField("Address:", address, fieldLen, nil, func(h string) {
address = h
})
for i := 0; i < 3; i++ {
field, ok := f.GetFormItem(i).(*tview.InputField)
if !ok {
continue
}
field.SetLabelColor(styles.LabelFgColor.Color())
field.SetFieldTextColor(styles.FieldFgColor.Color())
}
f.AddButton("OK", func() {
if coField.GetText() == "" || loField.GetText() == "" {
v.App().Flash().Err(fmt.Errorf("container to local port mismatch"))
return
}
tt, err := port.ToTunnels(address, coField.GetText(), loField.GetText())
if err != nil {
v.App().Flash().Err(err)
return
}
if err := okFn(v, path, tt); err != nil {
v.App().Flash().Err(err)
}
})
pages := v.App().Content.Pages
f.AddButton("Cancel", func() {
DismissPortForwards(v, pages)
})
for i := 0; i < 2; i++ {
b := f.GetButton(i)
if b == nil {
continue
}
b.SetBackgroundColorActivated(styles.ButtonFocusBgColor.Color())
b.SetLabelColorActivated(styles.ButtonFocusFgColor.Color())
}
modal := tview.NewModalForm("<PortForward>", f)
msg := path
if len(ports) > 1 {
msg += "\n\nExposed Ports:\n" + ports.Dump()
}
modal.SetText(msg)
modal.SetTextColor(styles.FgColor.Color())
modal.SetBackgroundColor(styles.BgColor.Color())
modal.SetDoneFunc(func(_ int, b string) {
DismissPortForwards(v, pages)
})
pages.AddPage(portForwardKey, modal, false, true)
pages.ShowPage(portForwardKey)
v.App().SetFocus(pages.GetPrimitive(portForwardKey))
}
// DismissPortForwards dismiss the port forward dialog.
func DismissPortForwards(v ResourceViewer, p *ui.Pages) {
p.RemovePage(portForwardKey)
v.App().SetFocus(p.CurrentPage().Item)
}
// ----------------------------------------------------------------------------
// Helpers...
func extractPort(port string) string {
tokens := strings.Split(port, "::")
if len(tokens) < 2 {
ports := strings.Split(port, ",")
for _, t := range ports {
if _, err := strconv.Atoi(strings.TrimSpace(t)); err != nil {
return ""
}
}
return port
}
return tokens[1]
}