bug fixes + maintenance items

mine
derailed 2019-07-11 23:47:03 -06:00
parent 1e179f8391
commit 970ab18637
25 changed files with 259 additions and 109 deletions

15
.dockerignore Normal file
View File

@ -0,0 +1,15 @@
/k8s
/change_logs
.github
.semaphore
.vscode
assets
/dist
/execs
/notes
/skins
README.md
LICENSE
cov.out
/k9s
.travis.yml

15
Dockerfile Normal file
View File

@ -0,0 +1,15 @@
# Build...
FROM golang:1.12.6-alpine AS build
WORKDIR /k9s
COPY go.mod go.sum main.go Makefile ./
COPY internal internal
COPY cmd cmd
RUN apk --no-cache add make git gcc libc-dev curl && make build
# -----------------------------------------------------------------------------
# Build Image...
FROM alpine:3.10.0
COPY --from=build /k9s/execs/k9s /bin/k9s
ENTRYPOINT [ "/bin/k9s" ]

View File

@ -0,0 +1,31 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.7.12
## 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. Your support, kindness and awesome suggestions to make K9s better is as always very much appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
## Change Logs
Maintenance release. Just code clean up and bug fixes.
---
## Resolved Bugs/Features
+ [Issue #259](https://github.com/derailed/k9s/issues/259)
+ [Issue #258](https://github.com/derailed/k9s/issues/258)
+ [Issue #256](https://github.com/derailed/k9s/issues/256)
+ [Issue #255](https://github.com/derailed/k9s/issues/255)
+ [Issue #252](https://github.com/derailed/k9s/issues/252)
+ [Issue #250](https://github.com/derailed/k9s/issues/250)
+ [Issue #246](https://github.com/derailed/k9s/issues/246)
---
<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

@ -90,9 +90,13 @@ func loadConfiguration() *config.Config {
if err := k9sCfg.Load(config.K9sConfigFile); err != nil { if err := k9sCfg.Load(config.K9sConfigFile); err != nil {
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...") log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
} }
k9sCfg.K9s.RefreshRate = refreshRate
if refreshRate != defaultRefreshRate {
k9sCfg.K9s.OverrideRefreshRate(refreshRate)
}
if err := k9sCfg.Refine(k8sFlags); err != nil { if err := k9sCfg.Refine(k8sFlags); err != nil {
log.Panic().Err(err).Msg("Refine Config") log.Panic().Err(err).Msg("Unable to locate K8s cluster configuration")
} }
k9sCfg.SetConnection(k8s.InitConnectionOrDie(k8sCfg, log.Logger)) k9sCfg.SetConnection(k8s.InitConnectionOrDie(k8sCfg, log.Logger))

2
go.mod
View File

@ -14,7 +14,7 @@ replace (
require ( require (
github.com/Azure/go-autorest/autorest v0.1.0 // indirect github.com/Azure/go-autorest/autorest v0.1.0 // indirect
github.com/derailed/tview v0.1.11 github.com/derailed/tview v0.1.12
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c // indirect
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect

5
go.sum
View File

@ -22,6 +22,7 @@ github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -35,10 +36,10 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/derailed/tview v0.1.10 h1:QWjK82ccTl3C7Tfyfmv765eRqEt/T3aXp40464cfnlw=
github.com/derailed/tview v0.1.10/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
github.com/derailed/tview v0.1.11 h1:aHe5bNiKC27qRLjjyu54Xoq6bRdtW3S0//r34rHzUbU= github.com/derailed/tview v0.1.11 h1:aHe5bNiKC27qRLjjyu54Xoq6bRdtW3S0//r34rHzUbU=
github.com/derailed/tview v0.1.11/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0= github.com/derailed/tview v0.1.11/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
github.com/derailed/tview v0.1.12 h1:EVTzx+Mq3PJzbGnCDwUVu5fD479mbQU/0rabxTm7tHA=
github.com/derailed/tview v0.1.12/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s=

View File

@ -1,6 +1,8 @@
package config package config
import "github.com/derailed/k9s/internal/k8s" import (
"github.com/derailed/k9s/internal/k8s"
)
const ( const (
defaultRefreshRate = 2 defaultRefreshRate = 2
@ -10,13 +12,14 @@ const (
// K9s tracks K9s configuration options. // K9s tracks K9s configuration options.
type K9s struct { type K9s struct {
RefreshRate int `yaml:"refreshRate"` RefreshRate int `yaml:"refreshRate"`
LogBufferSize int `yaml:"logBufferSize"` manualRefreshRate int
LogRequestSize int `yaml:"logRequestSize"` LogBufferSize int `yaml:"logBufferSize"`
CurrentContext string `yaml:"currentContext"` LogRequestSize int `yaml:"logRequestSize"`
CurrentCluster string `yaml:"currentCluster"` CurrentContext string `yaml:"currentContext"`
Clusters map[string]*Cluster `yaml:"clusters,omitempty"` CurrentCluster string `yaml:"currentCluster"`
Aliases map[string]string `yaml:"aliases,omitempty"` Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
Aliases map[string]string `yaml:"aliases,omitempty"`
} }
// NewK9s create a new K9s configuration. // NewK9s create a new K9s configuration.
@ -30,6 +33,21 @@ func NewK9s() *K9s {
} }
} }
// OverrideRefreshRate set the refresh rate manually.
func (k *K9s) OverrideRefreshRate(r int) {
k.manualRefreshRate = r
}
// GetRefreshRate returns the current refresh rate.
func (k *K9s) GetRefreshRate() int {
rate := k.RefreshRate
if k.manualRefreshRate != 0 {
rate = k.manualRefreshRate
}
return rate
}
// ActiveCluster returns the currently active cluster. // ActiveCluster returns the currently active cluster.
func (k *K9s) ActiveCluster() *Cluster { func (k *K9s) ActiveCluster() *Cluster {
if k.Clusters == nil { if k.Clusters == nil {

View File

@ -189,6 +189,9 @@ func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]stri
return err return err
} }
if len(pods) > 1 {
opts.MultiPods = true
}
pr := NewPod(b.Connection) pr := NewPod(b.Connection)
for _, p := range pods { for _, p := range pods {
po := p.(*v1.Pod) po := p.(*v1.Pod)

View File

@ -16,9 +16,11 @@ type (
LogOptions struct { LogOptions struct {
Fqn Fqn
Lines int64 Lines int64
Previous bool Previous bool
Color color.Paint Color color.Paint
SingleContainer bool
MultiPods bool
} }
) )
@ -50,10 +52,37 @@ func (o LogOptions) FixedSizeName() string {
return Truncate(strings.Join(s, "-"), 15) + "-" + tokens[len(tokens)-1] return Truncate(strings.Join(s, "-"), 15) + "-" + tokens[len(tokens)-1]
} }
// NormalizeName colorizes a pod name. // // NormalizeName colorizes a pod name.
func (o LogOptions) NormalizeName() string { // func (o LogOptions) NormalizeName() string {
if o.Color == 0 { // if o.Color == 0 {
// return ""
// }
// return color.Colorize(o.Name+":"+o.Container+" ", o.Color)
// // return o.Name + ":" + o.Container + " "
// }
func colorize(c color.Paint, txt string) string {
if c == 0 {
return "" return ""
} }
return color.Colorize(o.Name+":"+o.Container+" ", o.Color)
return color.Colorize(txt, c)
}
// DecorateLog add a log header to display po/co information along with the log message.
func (o LogOptions) DecorateLog(msg string) string {
if msg == "" {
return msg
}
if o.MultiPods {
return colorize(o.Color, o.Name+":"+o.Container+" ") + msg
}
if !o.SingleContainer {
return colorize(o.Color, o.Container+" ") + msg
}
return msg
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"io" "io"
"strconv" "strconv"
"sync/atomic"
"time" "time"
"github.com/derailed/k9s/internal/color" "github.com/derailed/k9s/internal/color"
@ -117,14 +118,6 @@ func (r *Pod) Containers(path string, includeInit bool) ([]string, error) {
return r.Resource.(k8s.Loggable).Containers(ns, po, includeInit) return r.Resource.(k8s.Loggable).Containers(ns, po, includeInit)
} }
func asColor(n string) color.Paint {
var sum int
for _, r := range n {
sum += int(r)
}
return color.Paint(30 + 1 + sum%6)
}
// PodLogs tail logs for all containers in a running Pod. // PodLogs tail logs for all containers in a running Pod.
func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
i := ctx.Value(IKey("informer")).(*watch.Informer) i := ctx.Value(IKey("informer")).(*watch.Informer)
@ -135,6 +128,10 @@ func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) err
po := p.(*v1.Pod) po := p.(*v1.Pod)
opts.Color = asColor(po.Name) opts.Color = asColor(po.Name)
if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 {
opts.SingleContainer = true
}
for _, co := range po.Spec.InitContainers { for _, co := range po.Spec.InitContainers {
opts.Container = co.Name opts.Container = co.Name
if err := r.Logs(ctx, c, opts); err != nil { if err := r.Logs(ctx, c, opts); err != nil {
@ -175,35 +172,47 @@ func tailLogs(ctx context.Context, res k8s.Loggable, c chan<- string, opts LogOp
Previous: opts.Previous, Previous: opts.Previous,
} }
req := res.Logs(opts.Namespace, opts.Name, &o) req := res.Logs(opts.Namespace, opts.Name, &o)
req.Context(ctx) ctxt, cancelFunc := context.WithCancel(ctx)
req.Context(ctxt)
var blocked int32 = 1
go logsTimeout(cancelFunc, &blocked)
// This call will block if nothing is in the stream!! // This call will block if nothing is in the stream!!
stream, err := req.Stream() stream, err := req.Stream()
atomic.StoreInt32(&blocked, 0)
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Stream canceled `%s", opts.Path()) log.Error().Err(err).Msgf("Log stream failed for `%s", opts.Path())
return err return fmt.Errorf("Unable to obtain log stream for %s", opts.Path())
} }
// atomic.StoreInt32(&blocked, 0)
go readLogs(ctx, stream, c, opts) go readLogs(ctx, stream, c, opts)
return nil return nil
} }
func logsTimeout(cancel context.CancelFunc, blocked *int32) {
select {
case <-time.After(defaultTimeout):
if atomic.LoadInt32(blocked) == 1 {
log.Debug().Msg("Timed out reading the log stream")
cancel()
}
}
}
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) { func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) {
defer func() { defer func() {
log.Debug().Msgf(">>> Closing stream `%s", opts.Path()) log.Debug().Msgf(">>> Closing stream `%s", opts.Path())
stream.Close() stream.Close()
}() }()
scanner, head := bufio.NewScanner(stream), opts.NormalizeName() scanner := bufio.NewScanner(stream)
for scanner.Scan() { for scanner.Scan() {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case c <- head + scanner.Text():
default: default:
// Ensures we get back to scanning c <- opts.DecorateLog(scanner.Text())
} }
} }
} }
@ -464,3 +473,13 @@ func (r *Pod) loggableContainers(s v1.PodStatus) []string {
} }
return rcos return rcos
} }
// Helpers..
func asColor(n string) color.Paint {
var sum int
for _, r := range n {
sum += int(r)
}
return color.Paint(30 + 2 + sum%6)
}

View File

@ -1,7 +1,5 @@
package views package views
import "github.com/rs/zerolog/log"
const maxStackSize = 10 const maxStackSize = 10
type cmdStack struct { type cmdStack struct {
@ -24,10 +22,10 @@ func (s *cmdStack) pop() (string, bool) {
if s.empty() { if s.empty() {
return "", false return "", false
} }
log.Info().Msgf("Before Pop %v", s.stack)
top := s.stack[len(s.stack)-1] top := s.stack[len(s.stack)-1]
s.stack = s.stack[:len(s.stack)-1] s.stack = s.stack[:len(s.stack)-1]
log.Info().Msgf("After Pop %v", s.stack)
return top, true return top, true
} }
@ -35,7 +33,6 @@ func (s *cmdStack) top() (string, bool) {
if s.empty() { if s.empty() {
return "", false return "", false
} }
log.Info().Msgf("Top %v -- %s", s.stack, s.stack[len(s.stack)-1])
return s.stack[len(s.stack)-1], true return s.stack[len(s.stack)-1], true
} }

View File

@ -70,7 +70,6 @@ func (c *command) isStdCmd(cmd string) bool {
} }
func (c *command) isAliasCmd(cmd string) bool { func (c *command) isAliasCmd(cmd string) bool {
cmds := make(map[string]resCmd, 30) cmds := make(map[string]resCmd, 30)
resourceViews(c.app.conn(), cmds) resourceViews(c.app.conn(), cmds)
res, ok := cmds[cmd] res, ok := cmds[cmd]

View File

@ -62,6 +62,12 @@ func newDetailsView(app *appView, backFn actionHandler) *detailsView {
app.Draw() app.Draw()
}) })
v.bindKeys()
return &v
}
func (v *detailsView) bindKeys() {
v.actions = keyActions{ v.actions = keyActions{
tcell.KeyBackspace2: newKeyAction("Erase", v.eraseCmd, false), tcell.KeyBackspace2: newKeyAction("Erase", v.eraseCmd, false),
tcell.KeyBackspace: newKeyAction("Erase", v.eraseCmd, false), tcell.KeyBackspace: newKeyAction("Erase", v.eraseCmd, false),
@ -70,8 +76,6 @@ func newDetailsView(app *appView, backFn actionHandler) *detailsView {
tcell.KeyTab: newKeyAction("Next Match", v.nextCmd, false), tcell.KeyTab: newKeyAction("Next Match", v.nextCmd, false),
tcell.KeyBacktab: newKeyAction("Previous Match", v.prevCmd, false), tcell.KeyBacktab: newKeyAction("Previous Match", v.prevCmd, false),
} }
return &v
} }
func (v *detailsView) setCategory(n string) { func (v *detailsView) setCategory(n string) {

View File

@ -29,20 +29,29 @@ type (
) )
func newHelpView(app *appView, current igniter) *helpView { func newHelpView(app *appView, current igniter) *helpView {
v := helpView{TextView: tview.NewTextView(), app: app, actions: make(keyActions)} v := helpView{
TextView: tview.NewTextView(),
app: app,
actions: make(keyActions),
}
v.SetTextColor(tcell.ColorAqua) v.SetTextColor(tcell.ColorAqua)
v.SetBorder(true) v.SetBorder(true)
v.SetBorderPadding(0, 0, 1, 1) v.SetBorderPadding(0, 0, 1, 1)
v.SetDynamicColors(true) v.SetDynamicColors(true)
v.SetInputCapture(v.keyboard) v.SetInputCapture(v.keyboard)
v.current = current v.current = current
v.bindKeys()
v.actions[tcell.KeyEsc] = newKeyAction("Back", v.backCmd, true)
v.actions[tcell.KeyEnter] = newKeyAction("Back", v.backCmd, false)
return &v return &v
} }
func (v *helpView) bindKeys() {
v.actions = keyActions{
tcell.KeyEsc: newKeyAction("Back", v.backCmd, true),
tcell.KeyEnter: newKeyAction("Back", v.backCmd, false),
}
}
func (v *helpView) keyboard(evt *tcell.EventKey) *tcell.EventKey { func (v *helpView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key() key := evt.Key()
if key == tcell.KeyRune { if key == tcell.KeyRune {
@ -108,7 +117,7 @@ func (v *helpView) showGeneral() {
{"Ctrl-r", "Refresh"}, {"Ctrl-r", "Refresh"},
{"Shift-i", "Invert Sort"}, {"Shift-i", "Invert Sort"},
{"p", "Previous resource view"}, {"p", "Previous resource view"},
{"q", "Quit"}, {":q", "Quit"},
} }
fmt.Fprintf(v, "🏠 [aqua::b]%s\n", "General") fmt.Fprintf(v, "🏠 [aqua::b]%s\n", "General")
for _, h := range general { for _, h := range general {

View File

@ -43,7 +43,7 @@ func TestNewHelpView(t *testing.T) {
v := newHelpView(a, nil) v := newHelpView(a, nil)
v.init(nil, "") v.init(nil, "")
const e = "🏠 General\n :<cmd> Command mode\n /<term> Filter mode\n esc Clear filter\n tab Next term match\n backtab Previous term match\n Ctrl-r Refresh\n Shift-i Invert Sort\n p Previous resource view\n q Quit\n\n🤖 View Navigation\n g Goto Top\n G Goto Bottom\n Ctrl-b Page Down\n Ctrl-f Page Up\n h Left\n l Right\n k Up\n j Down\n\n😱 Help\n ? Help\n Ctrl-a Aliases view\n" const e = "🏠 General\n :<cmd> Command mode\n /<term> Filter mode\n esc Clear filter\n tab Next term match\n backtab Previous term match\n Ctrl-r Refresh\n Shift-i Invert Sort\n p Previous resource view\n :q Quit\n\n🤖 View Navigation\n g Goto Top\n G Goto Bottom\n Ctrl-b Page Down\n Ctrl-f Page Up\n h Left\n l Right\n k Up\n j Down\n\n😱 Help\n ? Help\n Ctrl-a Aliases view\n"
assert.Equal(t, e, v.GetText(true)) assert.Equal(t, e, v.GetText(true))
assert.Equal(t, "Help", v.getTitle()) assert.Equal(t, "Help", v.getTitle())
} }

View File

@ -66,7 +66,7 @@ func newLogView(_ string, app *appView, backFn actionHandler) *logView {
v.logs.SetWrap(true) v.logs.SetWrap(true)
v.logs.SetMaxBuffer(app.config.K9s.LogBufferSize) v.logs.SetMaxBuffer(app.config.K9s.LogBufferSize)
} }
v.ansiWriter = tview.ANSIWriter(v.logs) v.ansiWriter = tview.ANSIWriter(v.logs, app.styles.Views().Log.FgColor, app.styles.Views().Log.BgColor)
v.status = newStatusView(app.styles) v.status = newStatusView(app.styles)
v.AddItem(v.status, 1, 1, false) v.AddItem(v.status, 1, 1, false)
v.AddItem(v.logs, 0, 1, true) v.AddItem(v.logs, 0, 1, true)
@ -119,8 +119,8 @@ func (v *logView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
func (v *logView) logLine(line string) { func (v *logView) log(lines string) {
fmt.Fprintln(v.ansiWriter, tview.Escape(line)) fmt.Fprintln(v.ansiWriter, tview.Escape(lines))
log.Debug().Msgf("LOG LINES %d", v.logs.GetLineCount()) log.Debug().Msgf("LOG LINES %d", v.logs.GetLineCount())
} }
@ -129,7 +129,7 @@ func (v *logView) flush(index int, buff []string) {
return return
} }
v.logLine(strings.Join(buff[:index], "\n")) v.log(strings.Join(buff[:index], "\n"))
if atomic.LoadInt32(&v.autoScroll) == 1 { if atomic.LoadInt32(&v.autoScroll) == 1 {
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {
v.update() v.update()

View File

@ -14,13 +14,13 @@ import (
func TestAnsi(t *testing.T) { func TestAnsi(t *testing.T) {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString("")
w := tview.ANSIWriter(buff) w := tview.ANSIWriter(buff, "white", "black")
fmt.Fprintf(w, "[YELLOW] ok") fmt.Fprintf(w, "[YELLOW] ok")
assert.Equal(t, "[YELLOW] ok", buff.String()) assert.Equal(t, "[YELLOW] ok", buff.String())
v := tview.NewTextView() v := tview.NewTextView()
v.SetDynamicColors(true) v.SetDynamicColors(true)
aw := tview.ANSIWriter(v) aw := tview.ANSIWriter(v, "white", "black")
s := "[2019-03-27T15:05:15,246][INFO ][o.e.c.r.a.AllocationService] [es-0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-6-2019.03.27][0]]" s := "[2019-03-27T15:05:15,246][INFO ][o.e.c.r.a.AllocationService] [es-0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-6-2019.03.27][0]]"
fmt.Fprintf(aw, s) fmt.Fprintf(aw, s)
assert.Equal(t, s+"\n", v.GetText(false)) assert.Equal(t, s+"\n", v.GetText(false))

View File

@ -86,13 +86,14 @@ func (v *logsView) load(container string, prevLogs bool) {
if err := v.doLoad(v.parent.getSelection(), container, prevLogs); err != nil { if err := v.doLoad(v.parent.getSelection(), container, prevLogs); err != nil {
v.app.flash().err(err) v.app.flash().err(err)
l := v.CurrentPage().Item.(*logView) l := v.CurrentPage().Item.(*logView)
l.logLine("😂 Doh! No logs are available at this time. Check again later on...") l.log("😂 Doh! No logs are available at this time. Check again later on...")
return return
} }
v.app.SetFocus(v) v.app.SetFocus(v)
} }
func (v *logsView) doLoad(path, co string, prevLogs bool) error { func (v *logsView) doLoad(path, co string, prevLogs bool) error {
log.Debug().Msgf("----Container %q", co)
v.stop() v.stop()
l := v.CurrentPage().Item.(*logView) l := v.CurrentPage().Item.(*logView)
@ -142,7 +143,6 @@ func updateLogs(c <-chan string, l *logView, buffSize int) {
l.flush(index, buff) l.flush(index, buff)
return return
} }
log.Debug().Msgf("Got line %s", line)
if index < buffSize { if index < buffSize {
buff[index] = line buff[index] = line
index++ index++

View File

@ -1,6 +1,7 @@
package views package views
import ( import (
"context"
"path" "path"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
@ -22,6 +23,7 @@ type (
*pageView *pageView
currentNS string currentNS string
title string
enterFn enterFn enterFn enterFn
extraActionsFn func(keyActions) extraActionsFn func(keyActions)
} }
@ -35,23 +37,23 @@ func newPageView(app *appView) *pageView {
} }
} }
func newMasterDetail(title string, app *appView, ns string) *masterDetail { func newMasterDetail(title, ns string, app *appView, backCmd actionHandler) *masterDetail {
v := masterDetail{ v := masterDetail{
pageView: newPageView(app), pageView: newPageView(app),
currentNS: ns, currentNS: ns,
title: title,
} }
tv := newTableView(v.app, v.title)
tv := newTableView(app, title)
tv.SetSelectionChangedFunc(v.selChanged) tv.SetSelectionChangedFunc(v.selChanged)
v.AddPage("master", tv, true, true) v.AddPage("master", tv, true, true)
details := newDetailsView(v.app, backCmd)
v.AddPage("details", details, true, false)
return &v return &v
} }
func (v *masterDetail) init(ns string, backCmd actionHandler) { func (v *masterDetail) init(ctx context.Context, ns string) {
details := newDetailsView(v.app, backCmd)
v.AddPage("details", details, true, false)
if v.currentNS != resource.NotNamespaced { if v.currentNS != resource.NotNamespaced {
v.currentNS = ns v.currentNS = ns
} }

View File

@ -2,7 +2,6 @@ package views
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
@ -150,15 +149,9 @@ func (v *podView) viewLogs(prev bool) bool {
if !v.rowSelected() { if !v.rowSelected() {
return false return false
} }
v.showLogs(v.selectedItem, "", v, prev)
status := trimCellRelative(v.masterPage(), v.selectedRow, 2) return true
if status == "Running" || status == "Completed" {
v.showLogs(v.selectedItem, "", v, prev)
return true
}
v.app.flash().err(errors.New("Selected pod is not running"))
return false
} }
func (v *podView) showLogs(path, co string, parent loggable, prev bool) { func (v *podView) showLogs(path, co string, parent loggable, prev bool) {

View File

@ -56,7 +56,7 @@ func (v *policyView) init(c context.Context, ns string) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second): case <-time.After(time.Duration(v.app.config.K9s.GetRefreshRate()) * time.Second):
v.refresh() v.refresh()
v.app.Draw() v.app.Draw()
} }

View File

@ -101,7 +101,7 @@ func (v *rbacView) init(c context.Context, ns string) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return return
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second): case <-time.After(time.Duration(v.app.config.K9s.GetRefreshRate()) * time.Second):
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {
v.refresh() v.refresh()
}) })

View File

@ -34,17 +34,20 @@ type (
func newResourceView(title string, app *appView, list resource.List) resourceViewer { func newResourceView(title string, app *appView, list resource.List) resourceViewer {
v := resourceView{ v := resourceView{
masterDetail: newMasterDetail(title, app, list.GetNamespace()), list: list,
list: list,
} }
v.masterPage().setFilterFn(v.filterResource) v.masterDetail = newMasterDetail(title, list.GetNamespace(), app, v.backCmd)
return &v return &v
} }
// Init watches all running pods in given namespace // Init watches all running pods in given namespace
func (v *resourceView) init(ctx context.Context, ns string) { func (v *resourceView) init(ctx context.Context, ns string) {
v.masterDetail.init(ns, v.backCmd) v.masterDetail.init(ctx, ns)
v.masterPage().setFilterFn(v.filterResource)
if v.colorerFn != nil {
v.masterPage().setColorer(v.colorerFn)
}
v.parentCtx = ctx v.parentCtx = ctx
var vctx context.Context var vctx context.Context
@ -69,7 +72,6 @@ func (v *resourceView) init(ctx context.Context, ns string) {
func (v *resourceView) setColorerFn(f colorerFn) { func (v *resourceView) setColorerFn(f colorerFn) {
v.colorerFn = f v.colorerFn = f
v.masterPage().setColorer(f)
} }
func (v *resourceView) setDecorateFn(f decorateFn) { func (v *resourceView) setDecorateFn(f decorateFn) {
@ -104,7 +106,7 @@ func (v *resourceView) update(ctx context.Context) {
case <-ctx.Done(): case <-ctx.Done():
log.Debug().Msgf("%s updater canceled!", v.list.GetName()) log.Debug().Msgf("%s updater canceled!", v.list.GetName())
return return
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second): case <-time.After(time.Duration(v.app.config.K9s.GetRefreshRate()) * time.Second):
v.app.QueueUpdateDraw(func() { v.app.QueueUpdateDraw(func() {
v.refresh() v.refresh()
}) })
@ -113,14 +115,31 @@ func (v *resourceView) update(ctx context.Context) {
}(ctx) }(ctx)
} }
// ----------------------------------------------------------------------------
// Actions...
func (v *resourceView) backCmd(*tcell.EventKey) *tcell.EventKey { func (v *resourceView) backCmd(*tcell.EventKey) *tcell.EventKey {
v.switchPage("master") v.switchPage("master")
return nil return nil
} }
func (v *resourceView) switchPage(p string) {
log.Debug().Msgf("Switching page to %s", p)
if _, ok := v.CurrentPage().Item.(*tableView); ok {
v.stopUpdates()
}
v.SwitchToPage(p)
v.currentNS = v.list.GetNamespace()
if vu, ok := v.GetPrimitive(p).(hinter); ok {
v.app.setHints(vu.hints())
}
if _, ok := v.CurrentPage().Item.(*tableView); ok {
v.restartUpdates()
}
}
// ----------------------------------------------------------------------------
// Actions...
func (v *resourceView) enterCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *resourceView) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
// If in command mode run filter otherwise enter function. // If in command mode run filter otherwise enter function.
if v.masterPage().filterCmd(evt) == nil || !v.rowSelected() { if v.masterPage().filterCmd(evt) == nil || !v.rowSelected() {
@ -163,7 +182,11 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
func (v *resourceView) defaultEnter(ns, resource, selection string) { func (v *resourceView) defaultEnter(ns, _, selection string) {
if !v.list.Access(resource.DescribeAccess) {
return
}
yaml, err := v.list.Resource().Describe(v.masterPage().baseTitle, selection) yaml, err := v.list.Resource().Describe(v.masterPage().baseTitle, selection)
if err != nil { if err != nil {
v.app.flash().errf("Describe command failed %s", err) v.app.flash().errf("Describe command failed %s", err)
@ -285,23 +308,6 @@ func (v *resourceView) refresh() {
v.selectItem(v.selectedRow, 0) v.selectItem(v.selectedRow, 0)
} }
func (v *resourceView) switchPage(p string) {
log.Debug().Msgf("Switching page to %s", p)
if _, ok := v.CurrentPage().Item.(*tableView); ok {
v.stopUpdates()
}
v.SwitchToPage(p)
v.currentNS = v.list.GetNamespace()
if vu, ok := v.GetPrimitive(p).(hinter); ok {
v.app.setHints(vu.hints())
}
if _, ok := v.CurrentPage().Item.(*tableView); ok {
v.restartUpdates()
}
}
func (v *resourceView) namespaceActions() { func (v *resourceView) namespaceActions() {
if !v.list.Access(resource.NamespaceAccess) { if !v.list.Access(resource.NamespaceAccess) {
return return

View File

@ -69,7 +69,7 @@ func (v *subjectView) init(c context.Context, _ string) {
case <-ctx.Done(): case <-ctx.Done():
log.Debug().Msgf("Subject:%s Watch bailing out!", v.subjectKind) log.Debug().Msgf("Subject:%s Watch bailing out!", v.subjectKind)
return return
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second): case <-time.After(time.Duration(v.app.config.K9s.GetRefreshRate()) * time.Second):
v.refresh() v.refresh()
v.app.Draw() v.app.Draw()
} }

View File

@ -116,10 +116,15 @@ func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
func (v *tableView) sortColCmd(col int) func(evt *tcell.EventKey) *tcell.EventKey { func (v *tableView) sortColCmd(col int) func(evt *tcell.EventKey) *tcell.EventKey {
return func(evt *tcell.EventKey) *tcell.EventKey { return func(evt *tcell.EventKey) *tcell.EventKey {
if col == -1 { v.sortCol.asc = true
v.sortCol.index, v.sortCol.asc = v.GetColumnCount()-1, true switch col {
} else { case -2:
v.sortCol.index, v.sortCol.asc = v.nameColIndex()+col, true v.sortCol.index = 0
case -1:
v.sortCol.index = v.GetColumnCount() - 1
default:
v.sortCol.index = v.nameColIndex() + col
} }
v.refresh() v.refresh()
@ -214,7 +219,7 @@ func (v *tableView) adjustSorter(data resource.TableData) {
func (v *tableView) doUpdate(data resource.TableData) { func (v *tableView) doUpdate(data resource.TableData) {
v.currentNS = data.Namespace v.currentNS = data.Namespace
if v.currentNS == resource.AllNamespaces && v.currentNS != "*" { if v.currentNS == resource.AllNamespaces && v.currentNS != "*" {
v.actions[KeyShiftP] = newKeyAction("Sort Namespace", v.sortColCmd(0), true) v.actions[KeyShiftP] = newKeyAction("Sort Namespace", v.sortColCmd(-2), true)
} else { } else {
delete(v.actions, KeyShiftP) delete(v.actions, KeyShiftP)
} }