Commit 2ceaa775 authored by derailed's avatar derailed
Browse files

switch the pick list for containers listing

Showing with 184 additions and 67 deletions
+184 -67
......@@ -121,7 +121,8 @@ K9s uses aliases to navigate most K8s resources.
| Command | Result | Example |
|-----------------------|----------------------------------------------------|----------------------------|
| `:`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` |
| `<Esc>` | Bails out of command mode | |
| `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) |
......
<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)
......@@ -114,7 +114,11 @@ func (c *Config) ActiveNamespace() string {
// FavNamespaces returns fav namespaces in the current cluster.
func (c *Config) FavNamespaces() []string {
return c.K9s.ActiveCluster().Namespace.Favorites
cl := c.K9s.ActiveCluster()
if cl != nil {
return c.K9s.ActiveCluster().Namespace.Favorites
}
return []string{}
}
// 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
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.
......
......@@ -35,14 +35,13 @@ func (k *K9s) ActiveCluster() *Cluster {
if k.Clusters == nil {
k.Clusters = map[string]*Cluster{}
}
if len(k.CurrentCluster) == 0 {
return nil
}
if c, ok := k.Clusters[k.CurrentCluster]; ok {
return c
}
k.Clusters[k.CurrentCluster] = NewCluster()
return k.Clusters[k.CurrentCluster]
}
......
......@@ -60,7 +60,7 @@ func TestK9sActiveClusterZero(t *testing.T) {
func TestK9sActiveClusterBlank(t *testing.T) {
var c config.K9s
cl := c.ActiveCluster()
assert.Nil(t, cl)
assert.Equal(t, config.NewCluster(), cl)
}
func TestK9sActiveCluster(t *testing.T) {
......
......@@ -250,6 +250,7 @@ func (a *APIClient) supportsMxServer() bool {
func (a *APIClient) SupportsRes(group string, versions []string) (string, bool) {
apiGroups, err := a.DialOrDie().Discovery().ServerGroups()
if err != nil {
log.Error().Err(err).Msg("Unable to dial api groups")
return "", false
}
......
......@@ -87,7 +87,7 @@ func NewApp(cfg *config.Config) *appView {
v.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", v.redrawCmd, false)
v.actions[tcell.KeyCtrlC] = newKeyAction("Quit", v.quitCmd, 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.KeyEnter] = newKeyAction("Goto", v.gotoCmd, false)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd, false)
......
......@@ -14,10 +14,18 @@ func newJobView(t string, app *appView, list resource.List) resourceViewer {
v := jobView{newResourceView(t, app, list).(*resourceView)}
{
v.extraActionsFn = v.extraActions
v.AddPage("logs", newLogsView(&v), true, false)
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
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
}
......@@ -41,7 +49,7 @@ func (v *jobView) getSelection() string {
// Handlers...
func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey {
func (v *jobView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
......@@ -49,22 +57,35 @@ func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey {
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
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
}
l := v.GetPrimitive("logs").(*logsView)
l.deleteAllPages()
for _, c := range cc {
l.addContainer(c)
if len(cc) == 1 {
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
return nil
}
v.switchPage("logs")
l.init()
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 *jobView) 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")
l.init()
}
func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyAction("Logs", v.logs, true)
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
}
......@@ -9,6 +9,7 @@ import (
type logView struct {
*detailsView
ansiWriter io.Writer
}
......@@ -21,13 +22,15 @@ func newLogView(title string, parent loggable) *logView {
v.SetWrap(true)
v.setTitle(parent.getSelection())
v.SetMaxBuffer(parent.appView().config.K9s.LogBufferSize)
v.ansiWriter = tview.ANSIWriter(v)
}
v.ansiWriter = tview.ANSIWriter(v)
return &v
}
func (l *logView) logLine(line string) {
fmt.Fprintln(l.ansiWriter, tview.Escape(line))
l.ScrollToEnd()
}
func (l *logView) log(lines fmt.Stringer) {
......
......@@ -21,16 +21,18 @@ const (
type logsView struct {
*tview.Pages
parentView string
parent loggable
containers []string
actions keyActions
cancelFunc context.CancelFunc
}
func newLogsView(parent loggable) *logsView {
func newLogsView(pview string, parent loggable) *logsView {
v := logsView{
Pages: tview.NewPages(),
parent: parent,
parentView: pview,
containers: []string{},
}
v.setActions(keyActions{
......@@ -93,6 +95,7 @@ func (v *logsView) hints() hints {
v.actions[tcell.Key(numKeys[i+1])] = newKeyAction(c, nil, true)
}
}
return v.actions.toHints()
}
......@@ -169,6 +172,7 @@ func (v *logsView) doLoad(path, co string) error {
return err
}
v.cancelFunc = cancelFn
return nil
}
......@@ -177,7 +181,8 @@ func (v *logsView) doLoad(path, co string) error {
func (v *logsView) back(evt *tcell.EventKey) *tcell.EventKey {
v.stop()
v.parent.switchPage(v.parent.getList().GetName())
v.parent.switchPage(v.parentView)
return nil
}
......@@ -186,6 +191,7 @@ func (v *logsView) top(evt *tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Top of logs...")
p.Item.(*logView).ScrollToBeginning()
}
return nil
}
......@@ -194,6 +200,7 @@ func (v *logsView) bottom(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Bottom of logs...")
p.Item.(*logView).ScrollToEnd()
}
return nil
}
......@@ -203,6 +210,7 @@ func (v *logsView) pageUp(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Reached Top ...")
}
}
return nil
}
......@@ -212,6 +220,7 @@ func (v *logsView) pageDown(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Reached Bottom ...")
}
}
return nil
}
......@@ -220,5 +229,6 @@ func (v *logsView) clearLogs(*tcell.EventKey) *tcell.EventKey {
v.parent.appView().flash(flashInfo, "Clearing logs...")
p.Item.(*logView).Clear()
}
return nil
}
package views
import (
"fmt"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/rs/zerolog/log"
......@@ -26,16 +24,17 @@ func newPodView(t string, app *appView, list resource.List) resourceViewer {
v.extraActionsFn = v.extraActions
}
v.AddPage("logs", newLogsView(&v), true, false)
picker := newSelectList()
picker := newSelectList(&v)
{
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")
return &v
}
......@@ -63,49 +62,64 @@ func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error().Err(err)
return evt
}
if len(cc) == 1 {
v.showLogs(v.selectedItem, cc[0], v.list.GetName(), v)
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()
for _, c := range cc {
l.addContainer(c)
}
l.addContainer(co)
v.switchPage("logs")
l.init()
return nil
}
func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem, false)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error().Msgf("Error fetching containers %v", err)
return evt
}
if len(cc) == 1 {
v.shellIn(v.selectedItem, "")
} else {
p := v.GetPrimitive("choose").(*selectList)
p.populate(cc)
p.SetSelectedFunc(func(i int, t, d string, r rune) {
v.shellIn(v.selectedItem, t)
})
v.switchPage("choose")
return nil
}
return evt
}
func (v *podView) showPicker(cc []string) {
l := v.GetPrimitive("choose").(*selectList)
l.populate(cc)
v.switchPage("choose")
p := v.GetPrimitive("picker").(*selectList)
p.populate(cc)
p.SetSelectedFunc(func(i int, t, d string, r rune) {
v.shellIn(v.selectedItem, t)
})
v.switchPage("picker")
return evt
}
func (v *podView) shellIn(path, co string) {
......@@ -123,22 +137,6 @@ func (v *podView) shellIn(path, co string) {
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) {
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
......
......@@ -329,7 +329,7 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
listFn: resource.NewHPAList,
}
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
......
package views
import (
"strconv"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
)
......@@ -10,13 +9,19 @@ import (
type selectList struct {
*tview.List
parent loggable
actions keyActions
}
func newSelectList() *selectList {
v := selectList{List: tview.NewList()}
func newSelectList(parent loggable) *selectList {
v := selectList{List: tview.NewList(), actions: keyActions{}}
{
v.parent = parent
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.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := v.actions[evt.Key()]; ok {
......@@ -26,9 +31,38 @@ func newSelectList() *selectList {
return evt
})
}
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.
func (v *selectList) setActions(aa keyActions) {
v.actions = aa
......@@ -38,12 +72,13 @@ func (v *selectList) hints() hints {
if v.actions != nil {
return v.actions.toHints()
}
return nil
}
func (v *selectList) populate(ss []string) {
v.Clear()
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)
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment