switch the pick list for containers listing
parent
3aca651403
commit
2ceaa775a7
|
|
@ -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) |
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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) extraActions(aa keyActions) {
|
||||
aa[KeyL] = newKeyAction("Logs", v.logs, true)
|
||||
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.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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue