forked from derailed/k9s
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
allow jumping to the owner of the resource (derailed#2700)
- Loading branch information
1 parent
d20d694
commit 491e668
Showing
10 changed files
with
288 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package dialog | ||
|
||
import ( | ||
"github.com/derailed/k9s/internal/config" | ||
"github.com/derailed/k9s/internal/ui" | ||
"github.com/derailed/tview" | ||
) | ||
|
||
type SelectAction func(index int) | ||
|
||
func ShowSelection(styles config.Dialog, pages *ui.Pages, title string, options []string, action SelectAction) { | ||
list := tview.NewList() | ||
list.ShowSecondaryText(false) | ||
list.SetSelectedTextColor(styles.ButtonFocusFgColor.Color()) | ||
list.SetSelectedBackgroundColor(styles.ButtonFocusBgColor.Color()) | ||
|
||
for _, option := range options { | ||
list.AddItem(option, "", 0, nil) | ||
list.AddItem(option, "", 0, nil) | ||
} | ||
|
||
modal := ui.NewModalList("<"+title+">", list) | ||
modal.SetDoneFunc(func(i int, s string) { | ||
dismiss(pages) | ||
action(i) | ||
}) | ||
|
||
pages.AddPage(dialogKey, modal, false, false) | ||
pages.ShowPage(dialogKey) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
package ui | ||
|
||
import ( | ||
"github.com/derailed/tcell/v2" | ||
"github.com/derailed/tview" | ||
) | ||
|
||
type ModalList struct { | ||
*tview.Box | ||
|
||
// The list embedded in the modal's frame. | ||
list *tview.List | ||
|
||
// The frame embedded in the modal. | ||
frame *tview.Frame | ||
|
||
// The optional callback for when the user clicked one of the items. It | ||
// receives the index of the clicked item and the item's text. | ||
done func(int, string) | ||
} | ||
|
||
func NewModalList(title string, list *tview.List) *ModalList { | ||
m := &ModalList{Box: tview.NewBox()} | ||
|
||
m.list = list | ||
m.list.SetBackgroundColor(tview.Styles.ContrastBackgroundColor).SetBorderPadding(0, 0, 0, 0) | ||
m.list.SetSelectedFunc(func(i int, main string, _ string, _ rune) { | ||
if m.done != nil { | ||
m.done(i, main) | ||
} | ||
}) | ||
m.list.SetDoneFunc(func() { | ||
if m.done != nil { | ||
m.done(-1, "") | ||
} | ||
}) | ||
|
||
m.frame = tview.NewFrame(m.list).SetBorders(0, 0, 1, 0, 0, 0) | ||
m.frame.SetBorder(true). | ||
SetBackgroundColor(tview.Styles.ContrastBackgroundColor). | ||
SetBorderPadding(1, 1, 1, 1) | ||
m.frame.SetTitle(title) | ||
m.frame.SetTitleColor(tcell.ColorAqua) | ||
|
||
return m | ||
} | ||
|
||
// Draw draws this primitive onto the screen. | ||
func (m *ModalList) Draw(screen tcell.Screen) { | ||
// Calculate the width of this modal. | ||
width := 0 | ||
for i := 0; i < m.list.GetItemCount(); i++ { | ||
main, secondary := m.list.GetItemText(i) | ||
width = max(width, len(main)+len(secondary)+2) | ||
} | ||
|
||
screenWidth, screenHeight := screen.Size() | ||
|
||
// Set the modal's position and size. | ||
height := m.list.GetItemCount() + 4 | ||
width += 2 | ||
x := (screenWidth - width) / 2 | ||
y := (screenHeight - height) / 2 | ||
m.SetRect(x, y, width, height) | ||
|
||
// Draw the frame. | ||
m.frame.SetRect(x, y, width, height) | ||
m.frame.Draw(screen) | ||
} | ||
|
||
func (m *ModalList) SetDoneFunc(handler func(int, string)) *ModalList { | ||
m.done = handler | ||
return m | ||
} | ||
|
||
// Focus is called when this primitive receives focus. | ||
func (m *ModalList) Focus(delegate func(p tview.Primitive)) { | ||
delegate(m.list) | ||
} | ||
|
||
// HasFocus returns whether this primitive has focus. | ||
func (m *ModalList) HasFocus() bool { | ||
return m.list.HasFocus() | ||
} | ||
|
||
// MouseHandler returns the mouse handler for this primitive. | ||
func (m *ModalList) MouseHandler() func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { | ||
return m.WrapMouseHandler(func(action tview.MouseAction, event *tcell.EventMouse, setFocus func(p tview.Primitive)) (consumed bool, capture tview.Primitive) { | ||
// Pass mouse events on to the form. | ||
consumed, capture = m.list.MouseHandler()(action, event, setFocus) | ||
if !consumed && action == tview.MouseLeftClick && m.InRect(event.Position()) { | ||
setFocus(m) | ||
consumed = true | ||
} | ||
return | ||
}) | ||
} | ||
|
||
// InputHandler returns the handler for this primitive. | ||
func (m *ModalList) InputHandler() func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { | ||
return m.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p tview.Primitive)) { | ||
if m.frame.HasFocus() { | ||
if handler := m.frame.InputHandler(); handler != nil { | ||
handler(event, setFocus) | ||
return | ||
} | ||
} | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package view | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"github.com/derailed/k9s/internal/ui/dialog" | ||
"github.com/rs/zerolog/log" | ||
|
||
"github.com/derailed/tcell/v2" | ||
"github.com/go-errors/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/runtime/schema" | ||
|
||
"github.com/derailed/k9s/internal" | ||
"github.com/derailed/k9s/internal/client" | ||
"github.com/derailed/k9s/internal/dao" | ||
"github.com/derailed/k9s/internal/render" | ||
"github.com/derailed/k9s/internal/ui" | ||
) | ||
|
||
// OwnerExtender adds owner actions to a given viewer. | ||
type OwnerExtender struct { | ||
ResourceViewer | ||
} | ||
|
||
// NewOwnerExtender returns a new extender. | ||
func NewOwnerExtender(r ResourceViewer) ResourceViewer { | ||
v := &OwnerExtender{ResourceViewer: r} | ||
v.AddBindKeysFn(v.bindKeys) | ||
|
||
return v | ||
} | ||
|
||
func (v *OwnerExtender) bindKeys(aa *ui.KeyActions) { | ||
aa.Add(ui.KeyShiftJ, ui.NewKeyAction("Jump Owner", v.ownerCmd, true)) | ||
} | ||
|
||
func (v *OwnerExtender) ownerCmd(evt *tcell.EventKey) *tcell.EventKey { | ||
path := v.GetTable().GetSelectedItem() | ||
if path == "" { | ||
return evt | ||
} | ||
|
||
if err := v.findOwnerFor(path); err != nil { | ||
log.Warn().Msgf("Unable to jump to the owner of resource %q: %s", path, err) | ||
v.App().Flash().Warnf("Unable to jump owner: %s", err) | ||
} | ||
return nil | ||
} | ||
|
||
func (v *OwnerExtender) findOwnerFor(path string) error { | ||
res, err := dao.AccessorFor(v.App().factory, v.GVR()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
o, err := res.Get(v.defaultCtx(), path) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
u, ok := v.asUnstructuredObject(o) | ||
if !ok { | ||
return errors.Errorf("unsupported object type: %t", o) | ||
} | ||
|
||
ns, _ := client.Namespaced(path) | ||
ownerReferences := u.GetOwnerReferences() | ||
if len(ownerReferences) == 1 { | ||
return v.jumpOwner(ns, ownerReferences[0]) | ||
} else if len(ownerReferences) > 1 { | ||
owners := make([]string, 0, len(ownerReferences)) | ||
for idx, ownerRef := range ownerReferences { | ||
owners = append(owners, fmt.Sprintf("%d: %s", idx, ownerRef.Kind)) | ||
} | ||
|
||
dialog.ShowSelection(v.App().Styles.Dialog(), v.App().Content.Pages, "Jump To", owners, func(index int) { | ||
if index >= 0 { | ||
err = v.jumpOwner(ns, ownerReferences[index]) | ||
} | ||
}) | ||
return err | ||
} | ||
|
||
return errors.Errorf("no owner found") | ||
} | ||
|
||
func (v *OwnerExtender) jumpOwner(ns string, owner metav1.OwnerReference) error { | ||
gv, err := schema.ParseGroupVersion(owner.APIVersion) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
gvr, found := dao.MetaAccess.GVK2GVR(gv, owner.Kind) | ||
if !found { | ||
return errors.Errorf("unsupported GVK: %s/%s", owner.APIVersion, owner.Kind) | ||
} | ||
|
||
v.App().gotoResource(gvr.String(), client.FQN(ns, owner.Name), false) | ||
return nil | ||
} | ||
|
||
func (v *OwnerExtender) defaultCtx() context.Context { | ||
return context.WithValue(context.Background(), internal.KeyFactory, v.App().factory) | ||
} | ||
|
||
func (v *OwnerExtender) asUnstructuredObject(o runtime.Object) (*unstructured.Unstructured, bool) { | ||
switch v := o.(type) { | ||
case *unstructured.Unstructured: | ||
return v, true | ||
case *render.PodWithMetrics: | ||
return v.Raw, true | ||
default: | ||
return nil, false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters