switch the pick list for containers listing

mine
derailed 2019-04-03 23:30:35 -06:00
parent 3aca651403
commit 2ceaa775a7
13 changed files with 186 additions and 69 deletions

View File

@ -121,7 +121,8 @@ K9s uses aliases to navigate most K8s resources.
| Command | Result | Example | | Command | Result | Example |
|-----------------------|----------------------------------------------------|----------------------------| |-----------------------|----------------------------------------------------|----------------------------|
| `:`alias`<ENTER>` | View a Kubernetes resource | `:po<ENTER>` | | `:`alias`<ENTER>` | View a Kubernetes resource | `:po<ENTER>` |
| '?' | Show all command aliases | select+<ENTER> to view | | `?` | Show keyboard shortcuts and help | |
| `A` | Show all available resource alias | select+`<ENTER>` to view |
| `/`filter`ENTER`> | Filter out a resource view given a filter | `/bumblebeetuna` | | `/`filter`ENTER`> | Filter out a resource view given a filter | `/bumblebeetuna` |
| `<Esc>` | Bails out of command mode | | | `<Esc>` | Bails out of command mode | |
| `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) | | `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) |

View File

@ -0,0 +1,42 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.4.5
## Notes
Thank you to all that contributed with flushing out issues with K9s! I'll try
to mark some of these issues as fixed. But if you don't mind grab the latest
rev and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.
Thank you so much for your support and awesome suggestions to make K9s better!!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
### Multi containers
There was an [issue](https://github.com/derailed/k9s/issues/135) where we ran into limitations with the container
selection keyboard shortcuts only allowing up to 10 containers. In this release, we've changed to a pick list vs the menu
to select containers for both shell and logs access. This gives K9s the ability to select up to 26 containers now. This
is not in any way an *encouragement* to have so many containers per pods!!
### Alias View ShortCut
The change above entailed having to move the alias shortcut to `A` vs `a` as the pick list shortcuts conflicted with
the alias view keyboard activation.
---
## Resolved Bugs
+ [Issue #152](https://github.com/derailed/k9s/issues/152)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -114,7 +114,11 @@ func (c *Config) ActiveNamespace() string {
// FavNamespaces returns fav namespaces in the current cluster. // FavNamespaces returns fav namespaces in the current cluster.
func (c *Config) FavNamespaces() []string { func (c *Config) FavNamespaces() []string {
cl := c.K9s.ActiveCluster()
if cl != nil {
return c.K9s.ActiveCluster().Namespace.Favorites return c.K9s.ActiveCluster().Namespace.Favorites
}
return []string{}
} }
// SetActiveNamespace set the active namespace in the current cluster. // SetActiveNamespace set the active namespace in the current cluster.
@ -136,7 +140,10 @@ func (c *Config) ActiveView() string {
// SetActiveView set the currently cluster active view // SetActiveView set the currently cluster active view
func (c *Config) SetActiveView(view string) { func (c *Config) SetActiveView(view string) {
c.K9s.ActiveCluster().View.Active = view cl := c.K9s.ActiveCluster()
if cl != nil {
cl.View.Active = view
}
} }
// GetConnection return an api server connection. // GetConnection return an api server connection.

View File

@ -35,14 +35,13 @@ func (k *K9s) ActiveCluster() *Cluster {
if k.Clusters == nil { if k.Clusters == nil {
k.Clusters = map[string]*Cluster{} k.Clusters = map[string]*Cluster{}
} }
if len(k.CurrentCluster) == 0 {
return nil
}
if c, ok := k.Clusters[k.CurrentCluster]; ok { if c, ok := k.Clusters[k.CurrentCluster]; ok {
return c return c
} }
k.Clusters[k.CurrentCluster] = NewCluster() k.Clusters[k.CurrentCluster] = NewCluster()
return k.Clusters[k.CurrentCluster] return k.Clusters[k.CurrentCluster]
} }

View File

@ -60,7 +60,7 @@ func TestK9sActiveClusterZero(t *testing.T) {
func TestK9sActiveClusterBlank(t *testing.T) { func TestK9sActiveClusterBlank(t *testing.T) {
var c config.K9s var c config.K9s
cl := c.ActiveCluster() cl := c.ActiveCluster()
assert.Nil(t, cl) assert.Equal(t, config.NewCluster(), cl)
} }
func TestK9sActiveCluster(t *testing.T) { func TestK9sActiveCluster(t *testing.T) {

View File

@ -250,6 +250,7 @@ func (a *APIClient) supportsMxServer() bool {
func (a *APIClient) SupportsRes(group string, versions []string) (string, bool) { func (a *APIClient) SupportsRes(group string, versions []string) (string, bool) {
apiGroups, err := a.DialOrDie().Discovery().ServerGroups() apiGroups, err := a.DialOrDie().Discovery().ServerGroups()
if err != nil { if err != nil {
log.Error().Err(err).Msg("Unable to dial api groups")
return "", false return "", false
} }

View File

@ -87,7 +87,7 @@ func NewApp(cfg *config.Config) *appView {
v.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", v.redrawCmd, false) v.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", v.redrawCmd, false)
v.actions[tcell.KeyCtrlC] = newKeyAction("Quit", v.quitCmd, false) v.actions[tcell.KeyCtrlC] = newKeyAction("Quit", v.quitCmd, false)
v.actions[KeyHelp] = newKeyAction("Help", v.helpCmd, false) v.actions[KeyHelp] = newKeyAction("Help", v.helpCmd, false)
v.actions[KeyA] = newKeyAction("Aliases", v.aliasCmd, true) v.actions[KeyShiftA] = newKeyAction("Aliases", v.aliasCmd, true)
v.actions[tcell.KeyEscape] = newKeyAction("Exit Cmd", v.deactivateCmd, false) v.actions[tcell.KeyEscape] = newKeyAction("Exit Cmd", v.deactivateCmd, false)
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, false) v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, false)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false) v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)

View File

@ -14,10 +14,18 @@ func newJobView(t string, app *appView, list resource.List) resourceViewer {
v := jobView{newResourceView(t, app, list).(*resourceView)} v := jobView{newResourceView(t, app, list).(*resourceView)}
{ {
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
v.AddPage("logs", newLogsView(&v), true, false) v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
v.switchPage("job") v.switchPage("job")
} }
picker := newSelectList(&v)
{
picker.setActions(keyActions{
tcell.KeyEscape: {description: "Back", action: v.backCmd, visible: true},
})
}
v.AddPage("picker", picker, true, false)
return &v return &v
} }
@ -41,7 +49,7 @@ func (v *jobView) getSelection() string {
// Handlers... // Handlers...
func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey { func (v *jobView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() { if !v.rowSelected() {
return evt return evt
} }
@ -49,22 +57,35 @@ func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey {
cc, err := fetchContainers(v.list, v.selectedItem, true) cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil { if err != nil {
v.app.flash(flashErr, err.Error()) v.app.flash(flashErr, err.Error())
log.Error().Err(err) log.Error().Err(err).Msgf("Unable to fetch containers for %s", v.selectedItem)
return evt return evt
} }
l := v.GetPrimitive("logs").(*logsView) if len(cc) == 1 {
l.deleteAllPages() v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
for _, c := range cc { return nil
l.addContainer(c)
} }
v.switchPage("logs") picker := v.GetPrimitive("picker").(*selectList)
l.init() picker.populate(cc)
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
v.showLogs(v.selectedItem, t, "picker", picker)
})
v.switchPage("picker")
return nil return nil
} }
func (v *jobView) extraActions(aa keyActions) { func (v *jobView) showLogs(path, co, view string, parent loggable) {
aa[KeyL] = newKeyAction("Logs", v.logs, true) l := v.GetPrimitive("logs").(*logsView)
l.parent = parent
l.parentView = view
l.deleteAllPages()
l.addContainer(co)
v.switchPage("logs")
l.init()
}
func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
} }

View File

@ -9,6 +9,7 @@ import (
type logView struct { type logView struct {
*detailsView *detailsView
ansiWriter io.Writer ansiWriter io.Writer
} }
@ -21,13 +22,15 @@ func newLogView(title string, parent loggable) *logView {
v.SetWrap(true) v.SetWrap(true)
v.setTitle(parent.getSelection()) v.setTitle(parent.getSelection())
v.SetMaxBuffer(parent.appView().config.K9s.LogBufferSize) v.SetMaxBuffer(parent.appView().config.K9s.LogBufferSize)
}
v.ansiWriter = tview.ANSIWriter(v) v.ansiWriter = tview.ANSIWriter(v)
}
return &v return &v
} }
func (l *logView) logLine(line string) { func (l *logView) logLine(line string) {
fmt.Fprintln(l.ansiWriter, tview.Escape(line)) fmt.Fprintln(l.ansiWriter, tview.Escape(line))
l.ScrollToEnd()
} }
func (l *logView) log(lines fmt.Stringer) { func (l *logView) log(lines fmt.Stringer) {

View File

@ -21,16 +21,18 @@ const (
type logsView struct { type logsView struct {
*tview.Pages *tview.Pages
parentView string
parent loggable parent loggable
containers []string containers []string
actions keyActions actions keyActions
cancelFunc context.CancelFunc cancelFunc context.CancelFunc
} }
func newLogsView(parent loggable) *logsView { func newLogsView(pview string, parent loggable) *logsView {
v := logsView{ v := logsView{
Pages: tview.NewPages(), Pages: tview.NewPages(),
parent: parent, parent: parent,
parentView: pview,
containers: []string{}, containers: []string{},
} }
v.setActions(keyActions{ v.setActions(keyActions{
@ -93,6 +95,7 @@ func (v *logsView) hints() hints {
v.actions[tcell.Key(numKeys[i+1])] = newKeyAction(c, nil, true) v.actions[tcell.Key(numKeys[i+1])] = newKeyAction(c, nil, true)
} }
} }
return v.actions.toHints() return v.actions.toHints()
} }
@ -169,6 +172,7 @@ func (v *logsView) doLoad(path, co string) error {
return err return err
} }
v.cancelFunc = cancelFn v.cancelFunc = cancelFn
return nil return nil
} }
@ -177,7 +181,8 @@ func (v *logsView) doLoad(path, co string) error {
func (v *logsView) back(evt *tcell.EventKey) *tcell.EventKey { func (v *logsView) back(evt *tcell.EventKey) *tcell.EventKey {
v.stop() v.stop()
v.parent.switchPage(v.parent.getList().GetName()) v.parent.switchPage(v.parentView)
return nil return nil
} }
@ -186,6 +191,7 @@ func (v *logsView) top(evt *tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Top of logs...") v.parent.appView().flash(flashInfo, "Top of logs...")
p.Item.(*logView).ScrollToBeginning() p.Item.(*logView).ScrollToBeginning()
} }
return nil return nil
} }
@ -194,6 +200,7 @@ func (v *logsView) bottom(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Bottom of logs...") v.parent.appView().flash(flashInfo, "Bottom of logs...")
p.Item.(*logView).ScrollToEnd() p.Item.(*logView).ScrollToEnd()
} }
return nil return nil
} }
@ -203,6 +210,7 @@ func (v *logsView) pageUp(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Reached Top ...") v.parent.appView().flash(flashInfo, "Reached Top ...")
} }
} }
return nil return nil
} }
@ -212,6 +220,7 @@ func (v *logsView) pageDown(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Reached Bottom ...") v.parent.appView().flash(flashInfo, "Reached Bottom ...")
} }
} }
return nil return nil
} }
@ -220,5 +229,6 @@ func (v *logsView) clearLogs(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Clearing logs...") v.parent.appView().flash(flashInfo, "Clearing logs...")
p.Item.(*logView).Clear() p.Item.(*logView).Clear()
} }
return nil return nil
} }

View File

@ -1,8 +1,6 @@
package views package views
import ( import (
"fmt"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -26,16 +24,17 @@ func newPodView(t string, app *appView, list resource.List) resourceViewer {
v.extraActionsFn = v.extraActions v.extraActionsFn = v.extraActions
} }
v.AddPage("logs", newLogsView(&v), true, false) picker := newSelectList(&v)
picker := newSelectList()
{ {
picker.setActions(keyActions{ picker.setActions(keyActions{
tcell.KeyEscape: {description: "Back", action: v.backCmd}, tcell.KeyEscape: {description: "Back", action: v.backCmd, visible: true},
}) })
v.AddPage("choose", picker, true, false)
} }
v.AddPage("picker", picker, true, false)
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
v.switchPage("po") v.switchPage("po")
return &v return &v
} }
@ -63,49 +62,64 @@ func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() { if !v.rowSelected() {
return evt return evt
} }
cc, err := fetchContainers(v.list, v.selectedItem, true) cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil { if err != nil {
v.app.flash(flashErr, err.Error()) v.app.flash(flashErr, err.Error())
log.Error().Err(err) log.Error().Err(err)
return evt return evt
} }
l := v.GetPrimitive("logs").(*logsView)
l.deleteAllPages() if len(cc) == 1 {
for _, c := range cc { v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
l.addContainer(c) return nil
} }
picker := v.GetPrimitive("picker").(*selectList)
picker.populate(cc)
picker.SetSelectedFunc(func(i int, t, d string, r rune) {
v.showLogs(v.selectedItem, t, "picker", picker)
})
v.switchPage("picker")
return nil
}
func (v *podView) showLogs(path, co, view string, parent loggable) {
l := v.GetPrimitive("logs").(*logsView)
l.parent = parent
l.parentView = view
l.deleteAllPages()
l.addContainer(co)
v.switchPage("logs") v.switchPage("logs")
l.init() l.init()
return nil
} }
func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() { if !v.rowSelected() {
return evt return evt
} }
cc, err := fetchContainers(v.list, v.selectedItem, false) cc, err := fetchContainers(v.list, v.selectedItem, false)
if err != nil { if err != nil {
v.app.flash(flashErr, err.Error()) v.app.flash(flashErr, err.Error())
log.Error().Msgf("Error fetching containers %v", err) log.Error().Msgf("Error fetching containers %v", err)
return evt return evt
} }
if len(cc) == 1 { if len(cc) == 1 {
v.shellIn(v.selectedItem, "") v.shellIn(v.selectedItem, "")
} else { return nil
p := v.GetPrimitive("choose").(*selectList) }
p := v.GetPrimitive("picker").(*selectList)
p.populate(cc) p.populate(cc)
p.SetSelectedFunc(func(i int, t, d string, r rune) { p.SetSelectedFunc(func(i int, t, d string, r rune) {
v.shellIn(v.selectedItem, t) v.shellIn(v.selectedItem, t)
}) })
v.switchPage("choose") v.switchPage("picker")
}
return evt
}
func (v *podView) showPicker(cc []string) { return evt
l := v.GetPrimitive("choose").(*selectList)
l.populate(cc)
v.switchPage("choose")
} }
func (v *podView) shellIn(path, co string) { func (v *podView) shellIn(path, co string) {
@ -123,22 +137,6 @@ func (v *podView) shellIn(path, co string) {
runK(v.app, args...) runK(v.app, args...)
} }
func (v *podView) showLogs(path, co string, previous bool) {
ns, po := namespaced(path)
args := make([]string, 0, 10)
args = append(args, "logs", "-f")
args = append(args, "-n", ns)
args = append(args, "--context", v.app.config.K9s.CurrentContext)
if len(co) != 0 {
args = append(args, "-c", co)
v.app.flash(flashInfo, fmt.Sprintf("Viewing logs from container %s on pod %s", co, po))
} else {
v.app.flash(flashInfo, fmt.Sprintf("Viewing logs from pod %s", po))
}
args = append(args, po)
runK(v.app, args...)
}
func (v *podView) extraActions(aa keyActions) { func (v *podView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true) aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true) aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)

View File

@ -329,7 +329,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
listFn: resource.NewHPAList, listFn: resource.NewHPAList,
} }
default: default:
log.Panic().Msgf("K9s does not currently support HPA version %s", rev) log.Panic().Msgf("K9s does not currently support HPA version `%s`", rev)
} }
return cmds return cmds

View File

@ -1,8 +1,7 @@
package views package views
import ( import (
"strconv" "github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
) )
@ -10,13 +9,19 @@ import (
type selectList struct { type selectList struct {
*tview.List *tview.List
parent loggable
actions keyActions actions keyActions
} }
func newSelectList() *selectList { func newSelectList(parent loggable) *selectList {
v := selectList{List: tview.NewList()} v := selectList{List: tview.NewList(), actions: keyActions{}}
{ {
v.parent = parent
v.SetBorder(true) v.SetBorder(true)
v.SetMainTextColor(tcell.ColorGray)
v.ShowSecondaryText(false)
v.SetShortcutColor(tcell.ColorAqua)
v.SetSelectedBackgroundColor(tcell.ColorAqua)
v.SetTitle(" [aqua::b]Container Selector ") v.SetTitle(" [aqua::b]Container Selector ")
v.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey { v.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := v.actions[evt.Key()]; ok { if a, ok := v.actions[evt.Key()]; ok {
@ -26,9 +31,38 @@ func newSelectList() *selectList {
return evt return evt
}) })
} }
return &v return &v
} }
func (v *selectList) back(evt *tcell.EventKey) *tcell.EventKey {
v.parent.switchPage(v.parent.getList().GetName())
return nil
}
// Protocol...
func (v *selectList) switchPage(p string) {
v.parent.switchPage(p)
}
func (v *selectList) backFn() actionHandler {
return v.parent.backFn()
}
func (v *selectList) appView() *appView {
return v.parent.appView()
}
func (v *selectList) getList() resource.List {
return v.parent.getList()
}
func (v *selectList) getSelection() string {
return v.parent.getSelection()
}
// SetActions to handle keyboard events. // SetActions to handle keyboard events.
func (v *selectList) setActions(aa keyActions) { func (v *selectList) setActions(aa keyActions) {
v.actions = aa v.actions = aa
@ -38,12 +72,13 @@ func (v *selectList) hints() hints {
if v.actions != nil { if v.actions != nil {
return v.actions.toHints() return v.actions.toHints()
} }
return nil return nil
} }
func (v *selectList) populate(ss []string) { func (v *selectList) populate(ss []string) {
v.Clear() v.Clear()
for i, s := range ss { for i, s := range ss {
v.AddItem(s, "Select a container", rune(strconv.Itoa(i)[0]), nil) v.AddItem(s, "Select a container", rune('a'+i), nil)
} }
} }