736 lines
17 KiB
Go
736 lines
17 KiB
Go
package view
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/derailed/k9s/internal"
|
|
"github.com/derailed/k9s/internal/client"
|
|
"github.com/derailed/k9s/internal/config"
|
|
"github.com/derailed/k9s/internal/dao"
|
|
"github.com/derailed/k9s/internal/model"
|
|
"github.com/derailed/k9s/internal/ui"
|
|
"github.com/derailed/k9s/internal/ui/dialog"
|
|
"github.com/derailed/k9s/internal/xray"
|
|
"github.com/derailed/tview"
|
|
"github.com/gdamore/tcell"
|
|
"github.com/rs/zerolog/log"
|
|
"github.com/sahilm/fuzzy"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
const xrayTitle = "Xray"
|
|
|
|
var _ ResourceViewer = (*Xray)(nil)
|
|
|
|
// Xray represents an xray tree view.
|
|
type Xray struct {
|
|
*ui.Tree
|
|
|
|
app *App
|
|
gvr client.GVR
|
|
meta metav1.APIResource
|
|
model *model.Tree
|
|
cancelFn context.CancelFunc
|
|
envFn EnvFunc
|
|
}
|
|
|
|
// NewXray returns a new view.
|
|
func NewXray(gvr client.GVR) ResourceViewer {
|
|
return &Xray{
|
|
gvr: gvr,
|
|
Tree: ui.NewTree(),
|
|
model: model.NewTree(gvr),
|
|
}
|
|
}
|
|
|
|
// Init initializes the view
|
|
func (x *Xray) Init(ctx context.Context) error {
|
|
x.envFn = x.k9sEnv
|
|
|
|
if err := x.Tree.Init(ctx); err != nil {
|
|
return err
|
|
}
|
|
x.SetKeyListenerFn(x.keyEntered)
|
|
|
|
var err error
|
|
x.meta, err = dao.MetaAccess.MetaFor(x.gvr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if x.app, err = extractApp(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
x.bindKeys()
|
|
x.SetBackgroundColor(x.app.Styles.Xray().BgColor.Color())
|
|
x.SetBorderColor(x.app.Styles.Xray().FgColor.Color())
|
|
x.SetBorderFocusColor(x.app.Styles.Frame().Border.FocusColor.Color())
|
|
x.SetGraphicsColor(x.app.Styles.Xray().GraphicColor.Color())
|
|
x.SetTitle(fmt.Sprintf(" %s-%s ", xrayTitle, strings.Title(x.gvr.R())))
|
|
|
|
x.model.SetRefreshRate(time.Duration(x.app.Config.K9s.GetRefreshRate()) * time.Second)
|
|
x.model.SetNamespace(client.CleanseNamespace(x.app.Config.ActiveNamespace()))
|
|
x.model.AddListener(x)
|
|
|
|
x.SetChangedFunc(func(n *tview.TreeNode) {
|
|
spec, ok := n.GetReference().(xray.NodeSpec)
|
|
if !ok {
|
|
log.Error().Msgf("No ref found on node %s", n.GetText())
|
|
return
|
|
}
|
|
x.SetSelectedItem(spec.AsPath())
|
|
x.refreshActions()
|
|
})
|
|
x.refreshActions()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExtraHints returns additional hints.
|
|
func (x *Xray) ExtraHints() map[string]string {
|
|
if !x.app.Styles.Xray().ShowIcons {
|
|
return nil
|
|
}
|
|
return xray.EmojiInfo()
|
|
}
|
|
|
|
// SetInstance sets specific resource instance.
|
|
func (x *Xray) SetInstance(string) {}
|
|
|
|
func (x *Xray) bindKeys() {
|
|
x.Actions().Add(ui.KeyActions{
|
|
tcell.KeyEnter: ui.NewKeyAction("Goto", x.gotoCmd, true),
|
|
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false),
|
|
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
|
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
|
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
|
tcell.KeyCtrlU: ui.NewSharedKeyAction("Clear Filter", x.clearCmd, false),
|
|
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", x.resetCmd, false),
|
|
})
|
|
}
|
|
|
|
func (x *Xray) keyEntered() {
|
|
x.ClearSelection()
|
|
x.update(x.filter(x.model.Peek()))
|
|
}
|
|
|
|
func (x *Xray) refreshActions() {
|
|
aa := make(ui.KeyActions)
|
|
|
|
defer func() {
|
|
pluginActions(x, aa)
|
|
hotKeyActions(x, aa)
|
|
|
|
x.Actions().Add(aa)
|
|
x.app.Menu().HydrateMenu(x.Hints())
|
|
}()
|
|
|
|
x.Actions().Clear()
|
|
x.bindKeys()
|
|
x.Tree.BindKeys()
|
|
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return
|
|
}
|
|
|
|
gvr := spec.GVR()
|
|
var err error
|
|
x.meta, err = dao.MetaAccess.MetaFor(client.NewGVR(gvr))
|
|
if err != nil {
|
|
log.Warn().Msgf("NO meta for %q -- %s", gvr, err)
|
|
return
|
|
}
|
|
|
|
if client.Can(x.meta.Verbs, "edit") {
|
|
aa[ui.KeyE] = ui.NewKeyAction("Edit", x.editCmd, true)
|
|
}
|
|
if client.Can(x.meta.Verbs, "delete") {
|
|
aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", x.deleteCmd, true)
|
|
}
|
|
if !dao.IsK9sMeta(x.meta) {
|
|
aa[ui.KeyY] = ui.NewKeyAction("YAML", x.viewCmd, true)
|
|
aa[ui.KeyD] = ui.NewKeyAction("Describe", x.describeCmd, true)
|
|
}
|
|
|
|
switch gvr {
|
|
case "v1/namespaces":
|
|
x.Actions().Delete(tcell.KeyEnter)
|
|
case "containers":
|
|
x.Actions().Delete(tcell.KeyEnter)
|
|
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
|
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true)
|
|
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
|
case "v1/pods":
|
|
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
|
aa[ui.KeyA] = ui.NewKeyAction("Attach", x.attachCmd, true)
|
|
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true)
|
|
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
|
}
|
|
x.Actions().Add(aa)
|
|
}
|
|
|
|
// GetSelectedPath returns the current selection as string.
|
|
func (x *Xray) GetSelectedPath() string {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return ""
|
|
}
|
|
return spec.Path()
|
|
}
|
|
|
|
func (x *Xray) selectedSpec() *xray.NodeSpec {
|
|
node := x.GetCurrentNode()
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
ref, ok := node.GetReference().(xray.NodeSpec)
|
|
if !ok {
|
|
log.Error().Msgf("Expecting a NodeSpec!")
|
|
return nil
|
|
}
|
|
|
|
return &ref
|
|
}
|
|
|
|
// EnvFn returns an plugin env function if available.
|
|
func (x *Xray) EnvFn() EnvFunc {
|
|
return x.envFn
|
|
}
|
|
|
|
func (x *Xray) k9sEnv() K9sEnv {
|
|
env := generalEnv(x.app)
|
|
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return env
|
|
}
|
|
|
|
env["FILTER"] = x.CmdBuff().String()
|
|
if env["FILTER"] == "" {
|
|
ns, n := client.Namespaced(spec.Path())
|
|
env["NAMESPACE"], env["FILTER"] = ns, n
|
|
}
|
|
|
|
switch spec.GVR() {
|
|
case "containers":
|
|
_, co := client.Namespaced(spec.Path())
|
|
env["CONTAINER"] = co
|
|
ns, n := client.Namespaced(*spec.ParentPath())
|
|
env["NAMESPACE"], env["POD"], env["NAME"] = ns, n, co
|
|
default:
|
|
ns, n := client.Namespaced(spec.Path())
|
|
env["NAMESPACE"], env["NAME"] = ns, n
|
|
}
|
|
|
|
return env
|
|
}
|
|
|
|
// Aliases returns all available aliases.
|
|
func (x *Xray) Aliases() []string {
|
|
return append(x.meta.ShortNames, x.meta.SingularName, x.meta.Name)
|
|
}
|
|
|
|
func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
|
return func(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return nil
|
|
}
|
|
|
|
x.showLogs(spec, prev)
|
|
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (x *Xray) showLogs(spec *xray.NodeSpec, prev bool) {
|
|
// Need to load and wait for pods
|
|
path, co := spec.Path(), ""
|
|
if spec.GVR() == "containers" {
|
|
_, coName := client.Namespaced(spec.Path())
|
|
path, co = *spec.ParentPath(), coName
|
|
}
|
|
|
|
ns, _ := client.Namespaced(path)
|
|
_, err := x.app.factory.CanForResource(ns, "v1/pods", client.MonitorAccess)
|
|
if err != nil {
|
|
x.app.Flash().Err(err)
|
|
return
|
|
}
|
|
|
|
if err := x.app.inject(NewLog(client.NewGVR("v1/pods"), path, co, prev)); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
}
|
|
|
|
func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return nil
|
|
}
|
|
|
|
if spec.Status() != "ok" {
|
|
x.app.Flash().Errf("%s is not in a running state", spec.Path())
|
|
return nil
|
|
}
|
|
|
|
path, co := spec.Path(), ""
|
|
if spec.GVR() == "containers" {
|
|
_, co = client.Namespaced(spec.Path())
|
|
path = *spec.ParentPath()
|
|
}
|
|
|
|
if err := containerShellin(x.app, x, path, co); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) attachCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return nil
|
|
}
|
|
|
|
if spec.Status() != "ok" {
|
|
x.app.Flash().Errf("%s is not in a running state", spec.Path())
|
|
return nil
|
|
}
|
|
|
|
path, co := spec.Path(), ""
|
|
if spec.GVR() == "containers" {
|
|
path = *spec.ParentPath()
|
|
}
|
|
|
|
if err := containerAttachIn(x.app, x, path, co); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return evt
|
|
}
|
|
|
|
ctx := x.defaultContext()
|
|
raw, err := x.model.ToYAML(ctx, spec.GVR(), spec.Path())
|
|
if err != nil {
|
|
x.App().Flash().Errf("unable to get resource %q -- %s", spec.GVR(), err)
|
|
return nil
|
|
}
|
|
|
|
details := NewDetails(x.app, "YAML", spec.Path(), true).Update(raw)
|
|
if err := x.app.inject(details); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return evt
|
|
}
|
|
|
|
x.Stop()
|
|
defer x.Start()
|
|
{
|
|
gvr := client.NewGVR(spec.GVR())
|
|
meta, err := dao.MetaAccess.MetaFor(gvr)
|
|
if err != nil {
|
|
log.Warn().Msgf("NO meta for %q -- %s", spec.GVR(), err)
|
|
return nil
|
|
}
|
|
x.resourceDelete(gvr, spec, fmt.Sprintf("Delete %s %s?", meta.SingularName, spec.Path()))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return evt
|
|
}
|
|
|
|
x.describe(spec.GVR(), spec.Path())
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) describe(gvr, path string) {
|
|
ctx := context.Background()
|
|
ctx = context.WithValue(ctx, internal.KeyFactory, x.app.factory)
|
|
|
|
yaml, err := x.model.Describe(ctx, gvr, path)
|
|
if err != nil {
|
|
x.app.Flash().Errf("Describe command failed: %s", err)
|
|
return
|
|
}
|
|
|
|
details := NewDetails(x.app, "Describe", path, true).Update(yaml)
|
|
if err := x.app.inject(details); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
}
|
|
|
|
func (x *Xray) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return evt
|
|
}
|
|
|
|
x.Stop()
|
|
defer x.Start()
|
|
{
|
|
ns, n := client.Namespaced(spec.Path())
|
|
args := make([]string, 0, 10)
|
|
args = append(args, "edit")
|
|
args = append(args, client.NewGVR(spec.GVR()).R())
|
|
args = append(args, "-n", ns)
|
|
args = append(args, "--context", x.app.Config.K9s.CurrentContext)
|
|
if cfg := x.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" {
|
|
args = append(args, "--kubeconfig", *cfg)
|
|
}
|
|
if !runK(x.app, shellOpts{args: append(args, n)}) {
|
|
x.app.Flash().Err(errors.New("Edit exec failed"))
|
|
}
|
|
}
|
|
|
|
return evt
|
|
}
|
|
|
|
func (x *Xray) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if x.app.InCmdMode() {
|
|
return evt
|
|
}
|
|
x.app.Flash().Info("Filter mode activated.")
|
|
x.CmdBuff().SetActive(true)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) clearCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !x.CmdBuff().IsActive() {
|
|
return evt
|
|
}
|
|
x.CmdBuff().Clear()
|
|
x.model.ClearFilter()
|
|
x.Start()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if x.CmdBuff().IsActive() {
|
|
x.CmdBuff().Delete()
|
|
}
|
|
x.UpdateTitle()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if !x.CmdBuff().InCmdMode() {
|
|
x.CmdBuff().Reset()
|
|
return x.app.PrevCmd(evt)
|
|
}
|
|
|
|
x.app.Flash().Info("Clearing filter...")
|
|
x.CmdBuff().Reset()
|
|
x.model.ClearFilter()
|
|
x.Start()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
if x.CmdBuff().IsActive() {
|
|
if ui.IsLabelSelector(x.CmdBuff().String()) {
|
|
x.Start()
|
|
}
|
|
x.CmdBuff().SetActive(false)
|
|
x.GetRoot().ExpandAll()
|
|
|
|
return nil
|
|
}
|
|
|
|
spec := x.selectedSpec()
|
|
if spec == nil {
|
|
return nil
|
|
}
|
|
log.Debug().Msgf("SELECTED REF %#v", spec)
|
|
if len(strings.Split(spec.Path(), "/")) == 1 {
|
|
return nil
|
|
}
|
|
if err := x.app.viewResource(client.NewGVR(spec.GVR()).R(), spec.Path(), false); err != nil {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (x *Xray) filter(root *xray.TreeNode) *xray.TreeNode {
|
|
q := x.CmdBuff().String()
|
|
if x.CmdBuff().Empty() || ui.IsLabelSelector(q) {
|
|
return root
|
|
}
|
|
|
|
x.UpdateTitle()
|
|
if ui.IsFuzzySelector(q) {
|
|
return root.Filter(q, fuzzyFilter)
|
|
}
|
|
|
|
return root.Filter(q, rxFilter)
|
|
}
|
|
|
|
// TreeNodeSelected callback for node selection.
|
|
func (x *Xray) TreeNodeSelected() {
|
|
x.app.QueueUpdateDraw(func() {
|
|
n := x.GetCurrentNode()
|
|
if n != nil {
|
|
n.SetColor(x.app.Styles.Xray().CursorColor.Color())
|
|
}
|
|
})
|
|
}
|
|
|
|
// TreeLoadFailed notifies the load failed.
|
|
func (x *Xray) TreeLoadFailed(err error) {
|
|
x.app.Flash().Err(err)
|
|
}
|
|
|
|
func (x *Xray) update(node *xray.TreeNode) {
|
|
root := makeTreeNode(node, x.ExpandNodes(), x.app.Styles)
|
|
if node == nil {
|
|
x.app.QueueUpdateDraw(func() {
|
|
x.SetRoot(root)
|
|
})
|
|
return
|
|
}
|
|
|
|
for _, c := range node.Children {
|
|
x.hydrate(root, c)
|
|
}
|
|
if x.GetSelectedItem() == "" {
|
|
x.SetSelectedItem(node.Spec().Path())
|
|
}
|
|
|
|
x.app.QueueUpdateDraw(func() {
|
|
x.SetRoot(root)
|
|
root.Walk(func(node, parent *tview.TreeNode) bool {
|
|
spec, ok := node.GetReference().(xray.NodeSpec)
|
|
if !ok {
|
|
log.Error().Msgf("Expecting a NodeSpec but got %T", node.GetReference())
|
|
return false
|
|
}
|
|
// BOZO!! Figure this out expand/collapse but the root
|
|
if parent != nil {
|
|
node.SetExpanded(x.ExpandNodes())
|
|
} else {
|
|
node.SetExpanded(true)
|
|
}
|
|
|
|
if spec.AsPath() == x.GetSelectedItem() {
|
|
log.Debug().Msgf("SEL %q--%q", spec.Path(), x.GetSelectedItem())
|
|
node.SetExpanded(true).SetSelectable(true)
|
|
x.SetCurrentNode(node)
|
|
}
|
|
return true
|
|
})
|
|
})
|
|
}
|
|
|
|
// TreeChanged notifies the model data changed.
|
|
func (x *Xray) TreeChanged(node *xray.TreeNode) {
|
|
x.Count = node.Count(x.gvr.String())
|
|
x.update(x.filter(node))
|
|
x.UpdateTitle()
|
|
}
|
|
|
|
func (x *Xray) hydrate(parent *tview.TreeNode, n *xray.TreeNode) {
|
|
node := makeTreeNode(n, x.ExpandNodes(), x.app.Styles)
|
|
for _, c := range n.Children {
|
|
x.hydrate(node, c)
|
|
}
|
|
parent.AddChild(node)
|
|
}
|
|
|
|
// SetEnvFn sets the custom environment function.
|
|
func (x *Xray) SetEnvFn(EnvFunc) {}
|
|
|
|
// Refresh updates the view
|
|
func (x *Xray) Refresh() {
|
|
}
|
|
|
|
// BufferChanged indicates the buffer was changed.
|
|
func (x *Xray) BufferChanged(s string) {}
|
|
|
|
// BufferActive indicates the buff activity changed.
|
|
func (x *Xray) BufferActive(state bool, k ui.BufferKind) {
|
|
x.app.BufferActive(state, k)
|
|
}
|
|
|
|
func (x *Xray) defaultContext() context.Context {
|
|
ctx := context.WithValue(context.Background(), internal.KeyFactory, x.app.factory)
|
|
ctx = context.WithValue(ctx, internal.KeyFields, "")
|
|
if x.CmdBuff().Empty() {
|
|
ctx = context.WithValue(ctx, internal.KeyLabels, "")
|
|
} else {
|
|
ctx = context.WithValue(ctx, internal.KeyLabels, ui.TrimLabelSelector(x.CmdBuff().String()))
|
|
}
|
|
|
|
return ctx
|
|
}
|
|
|
|
// Start initializes resource watch loop.
|
|
func (x *Xray) Start() {
|
|
x.Stop()
|
|
|
|
x.CmdBuff().AddListener(x.app.Cmd())
|
|
x.CmdBuff().AddListener(x)
|
|
|
|
ctx := x.defaultContext()
|
|
ctx, x.cancelFn = context.WithCancel(ctx)
|
|
x.model.Watch(ctx)
|
|
x.UpdateTitle()
|
|
}
|
|
|
|
// Stop terminates watch loop.
|
|
func (x *Xray) Stop() {
|
|
if x.cancelFn == nil {
|
|
return
|
|
}
|
|
x.cancelFn()
|
|
x.cancelFn = nil
|
|
|
|
x.CmdBuff().RemoveListener(x.app.Cmd())
|
|
x.CmdBuff().RemoveListener(x)
|
|
}
|
|
|
|
// SetBindKeysFn sets up extra key bindings.
|
|
func (x *Xray) SetBindKeysFn(BindKeysFunc) {}
|
|
|
|
// SetContextFn sets custom context.
|
|
func (x *Xray) SetContextFn(ContextFunc) {}
|
|
|
|
// Name returns the component name.
|
|
func (x *Xray) Name() string { return "XRay" }
|
|
|
|
// GetTable returns the underlying table.
|
|
func (x *Xray) GetTable() *Table { return nil }
|
|
|
|
// GVR returns a resource descriptor.
|
|
func (x *Xray) GVR() client.GVR { return x.gvr }
|
|
|
|
// App returns the current app handle.
|
|
func (x *Xray) App() *App {
|
|
return x.app
|
|
}
|
|
|
|
// UpdateTitle updates the view title.
|
|
func (x *Xray) UpdateTitle() {
|
|
t := x.styleTitle()
|
|
x.app.QueueUpdateDraw(func() {
|
|
x.SetTitle(t)
|
|
})
|
|
}
|
|
|
|
func (x *Xray) styleTitle() string {
|
|
base := fmt.Sprintf("%s-%s", xrayTitle, strings.Title(x.gvr.R()))
|
|
ns := x.model.GetNamespace()
|
|
if client.IsAllNamespaces(ns) {
|
|
ns = client.NamespaceAll
|
|
}
|
|
|
|
buff := x.CmdBuff().String()
|
|
var title string
|
|
if ns == client.ClusterScope {
|
|
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, x.Count), x.app.Styles.Frame())
|
|
} else {
|
|
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, x.Count), x.app.Styles.Frame())
|
|
}
|
|
if buff == "" {
|
|
return title
|
|
}
|
|
|
|
if ui.IsLabelSelector(buff) {
|
|
buff = ui.TrimLabelSelector(buff)
|
|
}
|
|
|
|
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), x.app.Styles.Frame())
|
|
}
|
|
|
|
func (x *Xray) resourceDelete(gvr client.GVR, spec *xray.NodeSpec, msg string) {
|
|
dialog.ShowDelete(x.app.Content.Pages, msg, func(cascade, force bool) {
|
|
x.app.Flash().Infof("Delete resource %s %s", spec.GVR(), spec.Path())
|
|
accessor, err := dao.AccessorFor(x.app.factory, gvr)
|
|
if err != nil {
|
|
log.Error().Err(err).Msgf("No accessor")
|
|
return
|
|
}
|
|
|
|
nuker, ok := accessor.(dao.Nuker)
|
|
if !ok {
|
|
x.app.Flash().Errf("Invalid nuker %T", accessor)
|
|
return
|
|
}
|
|
if err := nuker.Delete(spec.Path(), true, true); err != nil {
|
|
x.app.Flash().Errf("Delete failed with `%s", err)
|
|
} else {
|
|
x.app.Flash().Infof("%s `%s deleted successfully", x.GVR(), spec.Path())
|
|
x.app.factory.DeleteForwarder(spec.Path())
|
|
}
|
|
x.Refresh()
|
|
}, func() {})
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Helpers...
|
|
|
|
func fuzzyFilter(q, path string) bool {
|
|
q = strings.TrimSpace(q[2:])
|
|
mm := fuzzy.Find(q, []string{path})
|
|
|
|
return len(mm) > 0
|
|
}
|
|
|
|
func rxFilter(q, path string) bool {
|
|
rx := regexp.MustCompile(`(?i)` + q)
|
|
tokens := strings.Split(path, xray.PathSeparator)
|
|
for _, t := range tokens {
|
|
if rx.MatchString(t) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func makeTreeNode(node *xray.TreeNode, expanded bool, styles *config.Styles) *tview.TreeNode {
|
|
n := tview.NewTreeNode("No data...")
|
|
if node != nil {
|
|
n.SetText(node.Title(styles.Xray()))
|
|
n.SetReference(node.Spec())
|
|
}
|
|
n.SetSelectable(true)
|
|
n.SetExpanded(expanded)
|
|
n.SetColor(styles.Xray().CursorColor.Color())
|
|
n.SetSelectedFunc(func() {
|
|
n.SetExpanded(!n.IsExpanded())
|
|
})
|
|
return n
|
|
}
|