checkpoint
parent
ca51ce547b
commit
ffc31f856a
|
|
@ -0,0 +1,59 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.23.0
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues and enhancements for 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 are as ever very much noted and appreciated!
|
||||
|
||||
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorhip program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||
|
||||
---
|
||||
|
||||
## ♫ Sound Behind The Release ♭
|
||||
|
||||
I figured, why not share one of the tunes I was spinning when powering thru teh bugs? Might as well share the pain/pleasure while viewing this release notes!
|
||||
|
||||
[On An Island - David Gilmour With Crosby&Nash](https://www.youtube.com/watch?v=kEa__0wtIRo)
|
||||
|
||||
## A Word From Our Sponsors...
|
||||
|
||||
First off, I would like to send a `Big Thank You` to the following generous K9s friends for joining our sponsorship program and supporting this project!
|
||||
|
||||
* [Martin Kemp](https://github.com/MartiUK)
|
||||
|
||||
Contrarily to popular belief, OSS is not free! We've now reached ~9k stars and 300k downloads! As you all know, this project is not pimped out by a big company with deep pockets and a large team. K9s is complex and does demand a lot of my time. So if this tool is useful to you and part of your daily lifecycle, please contribute! Your contribution whether financial, PRs, issues or shout-outs on social/blogs are crucial to keep K9s growing and powerful for all of us. Don't let OSS by individual contributors become an oxymoron!
|
||||
## Best Effort... Not!
|
||||
|
||||
In this drop, we've added 2 new columns to the Pod/Container views namely `CPU(R:L)` and `MEM(R:L)`. These represents the current request/limit resources specified at either the pod or container level. While in Pod view, you will need to use the wide command `Ctrl-W` to see the resources set at the pod level or you can use K9s column customization feature to volunteer them by default.
|
||||
|
||||
## Container Images
|
||||
|
||||
You have now the ability to tweak your container images for experimentation, using the new SetImage binding aka `i`. This feature is available for standalone pods, deployments, sts and ds. With a resource selected, pressing `i` will provision an edit dialog listing all init/container images.
|
||||
|
||||
NOTE! This is a one shot commands applied directly against your cluster and won't survive a new resource deployment.
|
||||
|
||||
Big `ATTA Boy!` in effect to [Antoine Méausoone](https://github.com/Ameausoone) for putting forth the effort to make this feature available to all of us!!
|
||||
|
||||
## Resolved Issues/Features
|
||||
|
||||
* [Issue #906](https://github.com/derailed/k9s/issues/906) Print resources in pod view
|
||||
* [Issue #900](https://github.com/derailed/k9s/issues/900) Support sort by pending status
|
||||
* [Issue #895](https://github.com/derailed/k9s/issues/895) Wrong highlight position when filtering logs
|
||||
* [Issue #892](https://github.com/derailed/k9s/issues/892) tacit kustomize & kpt support
|
||||
* [Issue #889](https://github.com/derailed/k9s/issues/889) Disable read only config via command line flag
|
||||
*
|
||||
* [Issue #886](https://github.com/derailed/k9s/issues/886) Full screen mode or remove borders in YAML view for easy copy/paste
|
||||
* [Issue #887](https://github.com/derailed/k9s/issues/887) Ability to call out a separate program to parse/filter logs
|
||||
* [Issue #884](https://github.com/derailed/k9s/issues/884) Refresh for describe & yaml view
|
||||
* [Issue #883](https://github.com/derailed/k9s/issues/883) View logs quickly scrolls through entire logs when initially loading
|
||||
* [Issue #875](https://github.com/derailed/k9s/issues/875) Lazy filter
|
||||
* [Issue #848](https://github.com/derailed/k9s/issues/848) Support an inverse operator on filtered search
|
||||
|
||||
## Resolved PRs
|
||||
|
||||
---
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -26,7 +26,7 @@ const (
|
|||
var _ config.KubeSettings = (*client.Config)(nil)
|
||||
|
||||
var (
|
||||
version, commit, date = "dev", "dev", "n/a"
|
||||
version, commit, date = "dev", "dev", client.NA
|
||||
k9sFlags *config.Flags
|
||||
k8sFlags *genericclioptions.ConfigFlags
|
||||
demoMode = new(bool)
|
||||
|
|
@ -215,7 +215,7 @@ func initK9sFlags() {
|
|||
k9sFlags.ReadOnly,
|
||||
"readonly",
|
||||
false,
|
||||
"Disable all commands that modify the cluster",
|
||||
"Toggles readOnly mode by overriding configuration setting",
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -249,11 +249,12 @@ func (m *MetricsServer) PodsMetrics(pods *mv1beta1.PodMetricsList, mmx PodsMetri
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
const megaByte = 1024 * 1024
|
||||
// MegaByte represents a megabyte.
|
||||
const MegaByte = 1024 * 1024
|
||||
|
||||
// ToMB converts bytes to megabytes.
|
||||
func ToMB(v int64) int64 {
|
||||
return v / megaByte
|
||||
return v / MegaByte
|
||||
}
|
||||
|
||||
// ToPercentage computes percentage.
|
||||
|
|
|
|||
|
|
@ -29,14 +29,13 @@ func TestToPercentage(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestToMB(t *testing.T) {
|
||||
const mb = 1024 * 1024
|
||||
uu := []struct {
|
||||
v int64
|
||||
e int64
|
||||
}{
|
||||
{0, 0},
|
||||
{2 * mb, 2},
|
||||
{10 * mb, 10},
|
||||
{2 * client.MegaByte, 2},
|
||||
{10 * client.MegaByte, 10},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
|
|
|
|||
|
|
@ -34,8 +34,8 @@ func Colorize(s string, c Paint) string {
|
|||
}
|
||||
|
||||
// ANSIColorize colors a string.
|
||||
func ANSIColorize(s string, c int) string {
|
||||
return "\033[38;5;" + strconv.Itoa(c) + "m" + s + "\033[0m"
|
||||
func ANSIColorize(text string, color int) string {
|
||||
return "\033[38;5;" + strconv.Itoa(color) + "m" + text + "\033[0m"
|
||||
}
|
||||
|
||||
// Highlight colorize bytes at given indices.
|
||||
|
|
|
|||
|
|
@ -73,9 +73,9 @@ func (k *K9s) GetRefreshRate() int {
|
|||
}
|
||||
|
||||
// GetReadOnly returns the readonly setting.
|
||||
func (k *K9s) GetReadOnly() bool {
|
||||
func (k *K9s) IsReadOnly() bool {
|
||||
readOnly := k.ReadOnly
|
||||
if k.manualReadOnly != nil && *k.manualReadOnly {
|
||||
if k.manualReadOnly != nil {
|
||||
readOnly = *k.manualReadOnly
|
||||
}
|
||||
return readOnly
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ var (
|
|||
)
|
||||
|
||||
// Render returns a log line as string.
|
||||
func (l *LogItem) Render(c int, showTime bool) []byte {
|
||||
func (l *LogItem) Render(paint int, showTime bool) []byte {
|
||||
bb := make([]byte, 0, 200)
|
||||
if showTime {
|
||||
t := l.Timestamp
|
||||
|
|
@ -92,11 +92,11 @@ func (l *LogItem) Render(c int, showTime bool) []byte {
|
|||
}
|
||||
|
||||
if l.Pod != "" {
|
||||
bb = append(bb, color.ANSIColorize(l.Pod, c)...)
|
||||
bb = append(bb, color.ANSIColorize(l.Pod, paint)...)
|
||||
bb = append(bb, ':')
|
||||
}
|
||||
if !l.SingleContainer && l.Container != "" {
|
||||
bb = append(bb, color.ANSIColorize(l.Container, c)...)
|
||||
bb = append(bb, color.ANSIColorize(l.Container, paint)...)
|
||||
bb = append(bb, ' ')
|
||||
}
|
||||
|
||||
|
|
@ -122,10 +122,20 @@ func colorFor(n string) int {
|
|||
type LogItems []*LogItem
|
||||
|
||||
// Lines returns a collection of log lines.
|
||||
func (l LogItems) Lines() []string {
|
||||
func (l LogItems) Lines(showTime bool) [][]byte {
|
||||
ll := make([][]byte, len(l))
|
||||
for i, item := range l {
|
||||
ll[i] = item.Render(0, showTime)
|
||||
}
|
||||
|
||||
return ll
|
||||
}
|
||||
|
||||
// StrLines returns a collection of log lines.
|
||||
func (l LogItems) StrLines(showTime bool) []string {
|
||||
ll := make([]string, len(l))
|
||||
for i, item := range l {
|
||||
ll[i] = string(item.Render(0, false))
|
||||
ll[i] = string(item.Render(0, showTime))
|
||||
}
|
||||
|
||||
return ll
|
||||
|
|
@ -154,15 +164,15 @@ func (l LogItems) DumpDebug(m string) {
|
|||
}
|
||||
|
||||
// Filter filters out log items based on given filter.
|
||||
func (l LogItems) Filter(q string) ([]int, [][]int, error) {
|
||||
func (l LogItems) Filter(q string, showTime bool) ([]int, [][]int, error) {
|
||||
if q == "" {
|
||||
return nil, nil, nil
|
||||
}
|
||||
if IsFuzzySelector(q) {
|
||||
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]))
|
||||
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]), showTime)
|
||||
return mm, ii, nil
|
||||
}
|
||||
matches, indices, err := l.filterLogs(q)
|
||||
matches, indices, err := l.filterLogs(q, showTime)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Logs filter failed")
|
||||
return nil, nil, err
|
||||
|
|
@ -172,10 +182,10 @@ func (l LogItems) Filter(q string) ([]int, [][]int, error) {
|
|||
|
||||
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
||||
|
||||
func (l LogItems) fuzzyFilter(q string) ([]int, [][]int) {
|
||||
func (l LogItems) fuzzyFilter(q string, showTime bool) ([]int, [][]int) {
|
||||
q = strings.TrimSpace(q)
|
||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||
mm := fuzzy.Find(q, l.Lines())
|
||||
mm := fuzzy.Find(q, l.StrLines(showTime))
|
||||
for _, m := range mm {
|
||||
matches = append(matches, m.Index)
|
||||
indices = append(indices, m.MatchedIndexes)
|
||||
|
|
@ -184,14 +194,14 @@ func (l LogItems) fuzzyFilter(q string) ([]int, [][]int) {
|
|||
return matches, indices
|
||||
}
|
||||
|
||||
func (l LogItems) filterLogs(q string) ([]int, [][]int, error) {
|
||||
func (l LogItems) filterLogs(q string, showTime bool) ([]int, [][]int, error) {
|
||||
rx, err := regexp.Compile(`(?i)` + q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||
for i, line := range l.Lines() {
|
||||
if locs := rx.FindStringIndex(line); locs != nil {
|
||||
for i, line := range l.Lines(showTime) {
|
||||
if locs := rx.FindIndex(line); locs != nil {
|
||||
matches = append(matches, i)
|
||||
ii := make([]int, 0, 10)
|
||||
for i := 0; i < len(locs); i += 2 {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func TestLogItemsFilter(t *testing.T) {
|
|||
for _, i := range ii {
|
||||
i.Pod, i.Container = n, u.opts.Container
|
||||
}
|
||||
res, _, err := ii.Filter(u.q)
|
||||
res, _, err := ii.Filter(u.q, false)
|
||||
assert.Equal(t, u.err, err)
|
||||
if err == nil {
|
||||
assert.Equal(t, u.e, res)
|
||||
|
|
|
|||
|
|
@ -205,7 +205,6 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
|||
|
||||
var tailed bool
|
||||
for _, co := range po.Spec.InitContainers {
|
||||
log.Debug().Msgf("Tailing INIT-CO %q", co.Name)
|
||||
opts.Container = co.Name
|
||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||
return err
|
||||
|
|
@ -213,7 +212,6 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
|||
tailed = true
|
||||
}
|
||||
for _, co := range po.Spec.Containers {
|
||||
log.Debug().Msgf("Tailing CO %q", co.Name)
|
||||
opts.Container = co.Name
|
||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||
return err
|
||||
|
|
@ -221,7 +219,6 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
|||
tailed = true
|
||||
}
|
||||
for _, co := range po.Spec.EphemeralContainers {
|
||||
log.Debug().Msgf("Tailing EPH-CO %q", co.Name)
|
||||
opts.Container = co.Name
|
||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||
return err
|
||||
|
|
@ -236,7 +233,7 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ScanSA scans for serviceaccount refs.
|
||||
// ScanSA scans for ServiceAccount refs.
|
||||
func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
|
||||
ns, n := client.Namespaced(fqn)
|
||||
oo, err := p.Factory.List(p.GVR(), ns, wait, labels.Everything())
|
||||
|
|
@ -334,12 +331,10 @@ func tailLogs(ctx context.Context, logger Logger, c LogChan, opts LogOptions) er
|
|||
)
|
||||
done:
|
||||
for r := 0; r < logRetryCount; r++ {
|
||||
log.Debug().Msgf("Retry logs %d", r)
|
||||
req, err = logger.Logs(opts.Path, opts.ToPodLogOptions())
|
||||
if err == nil {
|
||||
// This call will block if nothing is in the stream!!
|
||||
if stream, err = req.Stream(ctx); err == nil {
|
||||
log.Debug().Msgf("Reading logs")
|
||||
go readLogs(stream, c, opts)
|
||||
break
|
||||
} else {
|
||||
|
|
@ -426,18 +421,18 @@ func extractFQN(o runtime.Object) string {
|
|||
u, ok := o.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o))
|
||||
return "na"
|
||||
return client.NA
|
||||
}
|
||||
m, ok := u.Object["metadata"].(map[string]interface{})
|
||||
if !ok {
|
||||
log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"]))
|
||||
return "na"
|
||||
return client.NA
|
||||
}
|
||||
|
||||
n, ok := m["name"].(string)
|
||||
if !ok {
|
||||
log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"]))
|
||||
return "na"
|
||||
return client.NA
|
||||
}
|
||||
|
||||
ns, ok := m["namespace"].(string)
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies {
|
|||
if nres[0] != '/' {
|
||||
nres = "/" + nres
|
||||
}
|
||||
pp = pp.Upsert(render.NewPolicyRes(ns, binding, nres, "n/a", rule.Verbs))
|
||||
pp = pp.Upsert(render.NewPolicyRes(ns, binding, nres, client.NA, rule.Verbs))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func NewCluster(f dao.Factory) *Cluster {
|
|||
func (c *Cluster) Version() string {
|
||||
info, err := c.factory.Client().ServerVersion()
|
||||
if err != nil {
|
||||
return NA
|
||||
return client.NA
|
||||
}
|
||||
|
||||
return info.GitVersion
|
||||
|
|
@ -55,7 +55,7 @@ func (c *Cluster) Version() string {
|
|||
func (c *Cluster) ContextName() string {
|
||||
n, err := c.factory.Client().Config().CurrentContextName()
|
||||
if err != nil {
|
||||
return NA
|
||||
return client.NA
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ func (c *Cluster) ContextName() string {
|
|||
func (c *Cluster) ClusterName() string {
|
||||
n, err := c.factory.Client().Config().CurrentClusterName()
|
||||
if err != nil {
|
||||
return NA
|
||||
return client.NA
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
|
@ -73,7 +73,7 @@ func (c *Cluster) ClusterName() string {
|
|||
func (c *Cluster) UserName() string {
|
||||
n, err := c.factory.Client().Config().CurrentUserName()
|
||||
if err != nil {
|
||||
return NA
|
||||
return client.NA
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ type ClusterInfoListener interface {
|
|||
ClusterInfoUpdated(ClusterMeta)
|
||||
}
|
||||
|
||||
// NA indicates data is missing at this time.
|
||||
const NA = "n/a"
|
||||
|
||||
// ClusterMeta represents cluster meta data.
|
||||
type ClusterMeta struct {
|
||||
Context, Cluster string
|
||||
|
|
@ -30,11 +27,11 @@ type ClusterMeta struct {
|
|||
// NewClusterMeta returns a new instance.
|
||||
func NewClusterMeta() ClusterMeta {
|
||||
return ClusterMeta{
|
||||
Context: NA,
|
||||
Cluster: NA,
|
||||
User: NA,
|
||||
K9sVer: NA,
|
||||
K8sVer: NA,
|
||||
Context: client.NA,
|
||||
Cluster: client.NA,
|
||||
User: client.NA,
|
||||
K9sVer: client.NA,
|
||||
K8sVer: client.NA,
|
||||
Cpu: 0,
|
||||
Mem: 0,
|
||||
Ephemeral: 0,
|
||||
|
|
|
|||
|
|
@ -1,25 +1,37 @@
|
|||
package model
|
||||
|
||||
const maxBuff = 10
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
maxBuff = 10
|
||||
|
||||
keyEntryDelay = 300 * time.Millisecond
|
||||
|
||||
// CommandBuffer represents a command buffer.
|
||||
CommandBuffer BufferKind = 1 << iota
|
||||
// FilterBuffer represents a filter buffer.
|
||||
FilterBuffer
|
||||
)
|
||||
|
||||
// BufferKind indicates a buffer type
|
||||
type BufferKind int8
|
||||
type (
|
||||
// BufferKind indicates a buffer type
|
||||
BufferKind int8
|
||||
|
||||
// BuffWatcher represents a command buffer listener.
|
||||
type BuffWatcher interface {
|
||||
// Changed indicates the buffer was changed.
|
||||
BufferChanged(s string)
|
||||
// BuffWatcher represents a command buffer listener.
|
||||
BuffWatcher interface {
|
||||
// BufferCompleted indicates input was accepted.
|
||||
BufferCompleted(s string)
|
||||
|
||||
// Active indicates the buff activity changed.
|
||||
BufferActive(state bool, kind BufferKind)
|
||||
}
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
BufferChanged(s string)
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
BufferActive(state bool, kind BufferKind)
|
||||
}
|
||||
)
|
||||
|
||||
// CmdBuff represents user command input.
|
||||
type CmdBuff struct {
|
||||
|
|
@ -28,6 +40,7 @@ type CmdBuff struct {
|
|||
hotKey rune
|
||||
kind BufferKind
|
||||
active bool
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// NewCmdBuff returns a new command buffer.
|
||||
|
|
@ -64,13 +77,24 @@ func (c *CmdBuff) GetText() string {
|
|||
// SetText initializes the buffer with a command.
|
||||
func (c *CmdBuff) SetText(cmd string) {
|
||||
c.buff = []rune(cmd)
|
||||
c.fireBufferChanged()
|
||||
c.fireBufferCompleted()
|
||||
}
|
||||
|
||||
// Add adds a new character to the buffer.
|
||||
func (c *CmdBuff) Add(r rune) {
|
||||
c.buff = append(c.buff, r)
|
||||
c.fireBufferChanged()
|
||||
if c.cancel != nil {
|
||||
return
|
||||
}
|
||||
var ctx context.Context
|
||||
ctx, c.cancel = context.WithTimeout(context.Background(), keyEntryDelay)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.fireBufferCompleted()
|
||||
c.cancel = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// Delete removes the last character from the buffer.
|
||||
|
|
@ -80,13 +104,25 @@ func (c *CmdBuff) Delete() {
|
|||
}
|
||||
c.buff = c.buff[:len(c.buff)-1]
|
||||
c.fireBufferChanged()
|
||||
if c.cancel != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var ctx context.Context
|
||||
ctx, c.cancel = context.WithTimeout(context.Background(), 800*time.Millisecond)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
c.fireBufferCompleted()
|
||||
c.cancel = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// ClearText clears out command buffer.
|
||||
func (c *CmdBuff) ClearText(fire bool) {
|
||||
c.buff = make([]rune, 0, maxBuff)
|
||||
if fire {
|
||||
c.fireBufferChanged()
|
||||
c.fireBufferCompleted()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +130,7 @@ func (c *CmdBuff) ClearText(fire bool) {
|
|||
func (c *CmdBuff) Reset() {
|
||||
c.ClearText(true)
|
||||
c.SetActive(false)
|
||||
c.fireBufferCompleted()
|
||||
}
|
||||
|
||||
// Empty returns true if no cmd, false otherwise.
|
||||
|
|
@ -125,6 +162,12 @@ func (c *CmdBuff) RemoveListener(l BuffWatcher) {
|
|||
c.listeners = append(c.listeners[:victim], c.listeners[victim+1:]...)
|
||||
}
|
||||
|
||||
func (c *CmdBuff) fireBufferCompleted() {
|
||||
for _, l := range c.listeners {
|
||||
l.BufferCompleted(c.GetText())
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CmdBuff) fireBufferChanged() {
|
||||
for _, l := range c.listeners {
|
||||
l.BufferChanged(c.GetText())
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@ func (l *testListener) BufferChanged(s string) {
|
|||
l.text = s
|
||||
}
|
||||
|
||||
func (l *testListener) BufferCompleted(s string) {
|
||||
l.text = s
|
||||
}
|
||||
|
||||
func (l *testListener) BufferActive(s bool, _ model.BufferKind) {
|
||||
if s {
|
||||
l.act++
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ func (m *mockSuggestionListener) BufferChanged(s string) {
|
|||
m.buff++
|
||||
}
|
||||
|
||||
func (m *mockSuggestionListener) BufferCompleted(s string) {
|
||||
}
|
||||
|
||||
func (m *mockSuggestionListener) BufferActive(state bool, kind model.BufferKind) {
|
||||
m.active = state
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import (
|
|||
// LogsListener represents a log model listener.
|
||||
type LogsListener interface {
|
||||
// LogChanged notifies the model changed.
|
||||
LogChanged(dao.LogItems)
|
||||
LogChanged([][]byte)
|
||||
|
||||
// LogCleanred indicates logs are cleared.
|
||||
LogCleared()
|
||||
|
|
@ -80,7 +80,7 @@ func (l *Log) SetLogOptions(opts dao.LogOptions) {
|
|||
|
||||
// Configure sets logger configuration.
|
||||
func (l *Log) Configure(opts *config.Logger) {
|
||||
l.logOptions.Lines = int64(opts.BufferSize)
|
||||
l.logOptions.Lines = int64(opts.TailCount)
|
||||
l.logOptions.SinceSeconds = opts.SinceSeconds
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +113,9 @@ func (l *Log) Clear() {
|
|||
// Refresh refreshes the logs.
|
||||
func (l *Log) Refresh() {
|
||||
l.fireLogCleared()
|
||||
l.fireLogChanged(l.lines)
|
||||
ll := make([][]byte, len(l.lines))
|
||||
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||
l.fireLogChanged(ll)
|
||||
}
|
||||
|
||||
// Restart restarts the logger.
|
||||
|
|
@ -149,7 +151,9 @@ func (l *Log) Set(items dao.LogItems) {
|
|||
l.mx.Unlock()
|
||||
|
||||
l.fireLogCleared()
|
||||
l.fireLogChanged(items)
|
||||
ll := make([][]byte, len(l.lines))
|
||||
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||
l.fireLogChanged(ll)
|
||||
}
|
||||
|
||||
// ClearFilter resets the log filter if any.
|
||||
|
|
@ -161,7 +165,9 @@ func (l *Log) ClearFilter() {
|
|||
l.mx.Unlock()
|
||||
|
||||
l.fireLogCleared()
|
||||
l.fireLogChanged(l.lines)
|
||||
ll := make([][]byte, len(l.lines))
|
||||
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||
l.fireLogChanged(ll)
|
||||
}
|
||||
|
||||
// Filter filters the model using either fuzzy or regexp.
|
||||
|
|
@ -169,6 +175,13 @@ func (l *Log) Filter(q string) {
|
|||
l.mx.Lock()
|
||||
defer l.mx.Unlock()
|
||||
|
||||
if len(q) == 0 {
|
||||
l.filter, l.filtering = "", false
|
||||
l.fireLogCleared()
|
||||
l.fireLogBuffChanged(l.lines)
|
||||
return
|
||||
}
|
||||
|
||||
l.filter = q
|
||||
if l.filtering {
|
||||
return
|
||||
|
|
@ -308,45 +321,48 @@ func (l *Log) RemoveListener(listener LogsListener) {
|
|||
}
|
||||
}
|
||||
|
||||
func applyFilter(q string, lines dao.LogItems) (dao.LogItems, error) {
|
||||
func (l *Log) applyFilter(q string) ([][]byte, error) {
|
||||
if q == "" {
|
||||
return lines, nil
|
||||
return nil, nil
|
||||
}
|
||||
matches, indices, err := lines.Filter(q)
|
||||
matches, indices, err := l.lines.Filter(q, l.logOptions.ShowTimestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No filter!
|
||||
if matches == nil {
|
||||
return lines, nil
|
||||
ll := make([][]byte, len(l.lines))
|
||||
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||
return ll, nil
|
||||
}
|
||||
// Blank filter
|
||||
if len(matches) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
filtered := make(dao.LogItems, 0, len(matches))
|
||||
filtered := make([][]byte, 0, len(matches))
|
||||
lines := l.lines.Lines(l.logOptions.ShowTimestamp)
|
||||
for i, idx := range matches {
|
||||
item := lines[idx].Clone()
|
||||
item.Bytes = color.Highlight(item.Bytes, indices[i], 209)
|
||||
filtered = append(filtered, item)
|
||||
filtered = append(filtered, color.Highlight(lines[idx], indices[i], 209))
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
|
||||
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
|
||||
filtered := lines
|
||||
if l.filter != "" {
|
||||
var err error
|
||||
filtered, err = applyFilter(l.filter, lines)
|
||||
ll := make([][]byte, len(l.lines))
|
||||
if l.filter == "" {
|
||||
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||
} else {
|
||||
ff, err := l.applyFilter(l.filter)
|
||||
if err != nil {
|
||||
l.fireLogError(err)
|
||||
return
|
||||
}
|
||||
ll = ff
|
||||
}
|
||||
if len(filtered) > 0 {
|
||||
l.fireLogChanged(filtered)
|
||||
if len(ll) > 0 {
|
||||
l.fireLogChanged(ll)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -356,7 +372,7 @@ func (l *Log) fireLogError(err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (l *Log) fireLogChanged(lines dao.LogItems) {
|
||||
func (l *Log) fireLogChanged(lines [][]byte) {
|
||||
for _, lis := range l.listeners {
|
||||
lis.LogChanged(lines)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ func newMockLogView() *mockLogView {
|
|||
return &mockLogView{}
|
||||
}
|
||||
|
||||
func (t *mockLogView) LogChanged(d dao.LogItems) {
|
||||
t.count += len(d.Lines())
|
||||
func (t *mockLogView) LogChanged(ll [][]byte) {
|
||||
t.count += len(ll)
|
||||
}
|
||||
func (t *mockLogView) LogCleared() {}
|
||||
func (t *mockLogView) LogFailed(err error) {}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func TestLogFullBuffer(t *testing.T) {
|
|||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, data[4:], v.data)
|
||||
assert.Equal(t, data[4:].Lines(false), v.data)
|
||||
}
|
||||
|
||||
func TestLogFilter(t *testing.T) {
|
||||
|
|
@ -144,7 +144,7 @@ func TestLogBasic(t *testing.T) {
|
|||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, data, v.data)
|
||||
assert.Equal(t, data.Lines(false), v.data)
|
||||
}
|
||||
|
||||
func TestLogAppend(t *testing.T) {
|
||||
|
|
@ -153,9 +153,11 @@ func TestLogAppend(t *testing.T) {
|
|||
|
||||
v := newTestView()
|
||||
m.AddListener(v)
|
||||
items := dao.LogItems{dao.NewLogItemFromString("blah blah")}
|
||||
items := dao.LogItems{
|
||||
dao.NewLogItemFromString("blah blah"),
|
||||
}
|
||||
m.Set(items)
|
||||
assert.Equal(t, items, v.data)
|
||||
assert.Equal(t, items.Lines(false), v.data)
|
||||
|
||||
data := dao.LogItems{
|
||||
dao.NewLogItemFromString("line1"),
|
||||
|
|
@ -165,13 +167,13 @@ func TestLogAppend(t *testing.T) {
|
|||
m.Append(d)
|
||||
}
|
||||
assert.Equal(t, 1, v.dataCalled)
|
||||
assert.Equal(t, items, v.data)
|
||||
assert.Equal(t, items.Lines(false), v.data)
|
||||
|
||||
m.Notify()
|
||||
assert.Equal(t, 2, v.dataCalled)
|
||||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
assert.Equal(t, append(items, data...), v.data)
|
||||
assert.Equal(t, append(items, data...).Lines(false), v.data)
|
||||
}
|
||||
|
||||
func TestLogTimedout(t *testing.T) {
|
||||
|
|
@ -196,7 +198,7 @@ func TestLogTimedout(t *testing.T) {
|
|||
assert.Equal(t, 1, v.clearCalled)
|
||||
assert.Equal(t, 0, v.errCalled)
|
||||
const e = "\x1b[38;5;209ml\x1b[0m\x1b[38;5;209mi\x1b[0m\x1b[38;5;209mn\x1b[0m\x1b[38;5;209me\x1b[0m\x1b[38;5;209m1\x1b[0m"
|
||||
assert.Equal(t, e, string(v.data[0].Bytes))
|
||||
assert.Equal(t, e, string(v.data[0]))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -213,7 +215,7 @@ func makeLogOpts(count int) dao.LogOptions {
|
|||
// ----------------------------------------------------------------------------
|
||||
|
||||
type testView struct {
|
||||
data dao.LogItems
|
||||
data [][]byte
|
||||
dataCalled int
|
||||
clearCalled int
|
||||
errCalled int
|
||||
|
|
@ -223,13 +225,13 @@ func newTestView() *testView {
|
|||
return &testView{}
|
||||
}
|
||||
|
||||
func (t *testView) LogChanged(d dao.LogItems) {
|
||||
t.data = d
|
||||
func (t *testView) LogChanged(ll [][]byte) {
|
||||
t.data = ll
|
||||
t.dataCalled++
|
||||
}
|
||||
func (t *testView) LogCleared() {
|
||||
t.clearCalled++
|
||||
t.data = dao.LogItems{}
|
||||
t.data = nil
|
||||
}
|
||||
func (t *testView) LogFailed(err error) {
|
||||
fmt.Println("LogErr", err)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestTableReconcile(t *testing.T) {
|
|||
err := ta.reconcile(ctx)
|
||||
assert.Nil(t, err)
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 18, len(data.Header))
|
||||
assert.Equal(t, 20, len(data.Header))
|
||||
assert.Equal(t, 1, len(data.RowEvents))
|
||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
||||
}
|
||||
|
|
@ -106,7 +106,7 @@ func TestTableHydrate(t *testing.T) {
|
|||
|
||||
assert.Nil(t, hydrate("blee", oo, rr, render.Pod{}))
|
||||
assert.Equal(t, 1, len(rr))
|
||||
assert.Equal(t, 18, len(rr[0].Fields))
|
||||
assert.Equal(t, 20, len(rr[0].Fields))
|
||||
}
|
||||
|
||||
func TestTableGenericHydrate(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ func TestTableRefresh(t *testing.T) {
|
|||
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
||||
ta.Refresh(ctx)
|
||||
data := ta.Peek()
|
||||
assert.Equal(t, 18, len(data.Header))
|
||||
assert.Equal(t, 20, len(data.Header))
|
||||
assert.Equal(t, 1, len(data.RowEvents))
|
||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
||||
assert.Equal(t, 1, l.count)
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ func (Container) Header(ns string) Header {
|
|||
HeaderColumn{Name: "INIT"},
|
||||
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "PROBES(L:R)"},
|
||||
HeaderColumn{Name: "CPU(R:L)", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "MEM(R:L)", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true},
|
||||
|
|
@ -95,7 +97,7 @@ func (c Container) Render(o interface{}, name string, r *Row) error {
|
|||
return fmt.Errorf("Expected ContainerRes, but got %T", o)
|
||||
}
|
||||
|
||||
cur, perc, limit := gatherMetrics(co.Container, co.MX)
|
||||
cur, perc, limit, res := gatherMetrics(co.Container, co.MX)
|
||||
ready, state, restarts := "false", MissingValue, "0"
|
||||
if co.Status != nil {
|
||||
ready, state, restarts = boolToStr(co.Status.Ready), ToContainerState(co.Status.State), strconv.Itoa(int(co.Status.RestartCount))
|
||||
|
|
@ -111,6 +113,8 @@ func (c Container) Render(o interface{}, name string, r *Row) error {
|
|||
boolToStr(co.IsInit),
|
||||
restarts,
|
||||
probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe),
|
||||
ToResourcesMc(res),
|
||||
ToResourcesMi(res),
|
||||
cur.cpu,
|
||||
cur.mem,
|
||||
perc.cpu,
|
||||
|
|
@ -140,8 +144,24 @@ func (Container) diagnose(state, ready string) error {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l metric) {
|
||||
func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l metric, r resources) {
|
||||
c, p, l = noMetric(), noMetric(), noMetric()
|
||||
|
||||
r = make(resources, 4)
|
||||
rcpu, rmem := containerResources(*co)
|
||||
lcpu, lmem := containerLimits(*co)
|
||||
if rcpu != nil {
|
||||
r[requestCPU] = rcpu
|
||||
}
|
||||
if rmem != nil {
|
||||
r[requestMEM] = rmem
|
||||
}
|
||||
if lcpu != nil {
|
||||
r[limitCPU] = lcpu
|
||||
}
|
||||
if lmem != nil {
|
||||
r[limitMEM] = lmem
|
||||
}
|
||||
if mx == nil {
|
||||
return
|
||||
}
|
||||
|
|
@ -149,11 +169,10 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l met
|
|||
cpu := mx.Usage.Cpu().MilliValue()
|
||||
mem := client.ToMB(mx.Usage.Memory().Value())
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu),
|
||||
cpu: ToMc(cpu),
|
||||
mem: ToMi(mem),
|
||||
}
|
||||
|
||||
rcpu, rmem := containerResources(*co)
|
||||
if rcpu != nil {
|
||||
p.cpu = client.ToPercentageStr(cpu, rcpu.MilliValue())
|
||||
}
|
||||
|
|
@ -161,7 +180,6 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l met
|
|||
p.mem = client.ToPercentageStr(mem, client.ToMB(rmem.Value()))
|
||||
}
|
||||
|
||||
lcpu, lmem := containerLimits(*co)
|
||||
if lcpu != nil {
|
||||
l.cpu = client.ToPercentageStr(cpu, lcpu.MilliValue())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,10 @@ func TestContainer(t *testing.T) {
|
|||
"false",
|
||||
"0",
|
||||
"off:off",
|
||||
"10",
|
||||
"20",
|
||||
"20m/20m",
|
||||
"100Mi/100Mi",
|
||||
"10m",
|
||||
"20Mi",
|
||||
"50",
|
||||
"20",
|
||||
"50",
|
||||
|
|
|
|||
|
|
@ -57,13 +57,13 @@ func extractMetaField(m map[string]interface{}, field string) string {
|
|||
f, ok := m[field]
|
||||
if !ok {
|
||||
log.Error().Err(fmt.Errorf("failed to extract field from meta %s", field))
|
||||
return "n/a"
|
||||
return NAValue
|
||||
}
|
||||
|
||||
fs, ok := f.(string)
|
||||
if !ok {
|
||||
log.Error().Err(fmt.Errorf("failed to extract string from field %s", field))
|
||||
return "n/a"
|
||||
return NAValue
|
||||
}
|
||||
|
||||
return fs
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/tview"
|
||||
runewidth "github.com/mattn/go-runewidth"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
|
@ -63,13 +64,6 @@ func Happy(ns string, h Header, r Row) bool {
|
|||
return strings.TrimSpace(r.Fields[validCol]) == ""
|
||||
}
|
||||
|
||||
// const megaByte = 1024 * 1024
|
||||
|
||||
// // ToMB converts bytes to megabytes.
|
||||
// func ToMB(v int64) float64 {
|
||||
// return float64(v) / megaByte
|
||||
// }
|
||||
|
||||
func asStatus(err error) string {
|
||||
if err == nil {
|
||||
return ""
|
||||
|
|
@ -257,14 +251,64 @@ func mapToIfc(m interface{}) (s string) {
|
|||
return
|
||||
}
|
||||
|
||||
// ToMillicore shows cpu reading for human.
|
||||
func ToMillicore(v int64) string {
|
||||
return strconv.Itoa(int(v))
|
||||
// ToResourcesMi prints out request:limit mem resources.
|
||||
func ToResourcesMi(res resources) string {
|
||||
var v1, v2 int64
|
||||
if v, ok := res[requestMEM]; ok && v != nil {
|
||||
v1 = v.MilliValue()
|
||||
}
|
||||
if v, ok := res[limitMEM]; ok && v != nil {
|
||||
v2 = v.MilliValue()
|
||||
}
|
||||
if v1 == 0 && v2 == 0 {
|
||||
return NAValue
|
||||
}
|
||||
return bytesToMb(v1) + ":" + bytesToMb(v2)
|
||||
}
|
||||
|
||||
// ToMi shows mem reading for human.
|
||||
func toMc(v int64) string {
|
||||
if v == 0 {
|
||||
return NAValue
|
||||
}
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%dm", v)
|
||||
}
|
||||
|
||||
func bytesToMb(v int64) string {
|
||||
if v == 0 {
|
||||
return NAValue
|
||||
}
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%dMi", v/(client.MegaByte*1_000))
|
||||
|
||||
}
|
||||
|
||||
// ToResourcesMc prints out request:limit cpu resources.
|
||||
func ToResourcesMc(res resources) string {
|
||||
var v1, v2 int64
|
||||
if v, ok := res[requestCPU]; ok && v != nil {
|
||||
v1 = v.MilliValue()
|
||||
}
|
||||
if v, ok := res[limitCPU]; ok && v != nil {
|
||||
v2 = v.MilliValue()
|
||||
}
|
||||
if v1 == 0 && v2 == 0 {
|
||||
return NAValue
|
||||
}
|
||||
|
||||
return toMc(v1) + ":" + toMc(v2)
|
||||
}
|
||||
|
||||
// ToMc returns a the millicore unit.
|
||||
func ToMc(v int64) string {
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%dm", v)
|
||||
}
|
||||
|
||||
// ToMi returns the megabytes unit.
|
||||
func ToMi(v int64) string {
|
||||
return strconv.Itoa(int(v))
|
||||
p := message.NewPrinter(language.English)
|
||||
return p.Sprintf("%dMi", v)
|
||||
}
|
||||
|
||||
func boolPtrToStr(b *bool) string {
|
||||
|
|
|
|||
|
|
@ -361,18 +361,18 @@ func BenchmarkMapToStr(b *testing.B) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestToMillicore(t *testing.T) {
|
||||
func TestToMc(t *testing.T) {
|
||||
uu := []struct {
|
||||
v int64
|
||||
e string
|
||||
}{
|
||||
{0, "0"},
|
||||
{2, "2"},
|
||||
{1000, "1000"},
|
||||
{0, "0m"},
|
||||
{2, "2m"},
|
||||
{1000, "1,000m"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, ToMillicore(u.v))
|
||||
assert.Equal(t, u.e, ToMc(u.v))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -381,9 +381,9 @@ func TestToMi(t *testing.T) {
|
|||
v int64
|
||||
e string
|
||||
}{
|
||||
{0, "0"},
|
||||
{2, "2"},
|
||||
{1000, "1000"},
|
||||
{0, "0Mi"},
|
||||
{2, "2Mi"},
|
||||
{1000, "1,000Mi"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
|
|
|
|||
|
|
@ -159,13 +159,13 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p
|
|||
|
||||
cpu, mem := mx.Usage.Cpu().MilliValue(), client.ToMB(mx.Usage.Memory().Value())
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu),
|
||||
cpu: ToMc(cpu),
|
||||
mem: ToMi(mem),
|
||||
}
|
||||
|
||||
acpu, amem := no.Status.Allocatable.Cpu().MilliValue(), client.ToMB(no.Status.Allocatable.Memory().Value())
|
||||
a = metric{
|
||||
cpu: ToMillicore(acpu),
|
||||
cpu: ToMc(acpu),
|
||||
mem: ToMi(amem),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestNodeRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "minikube", r.ID)
|
||||
e := render.Fields{"minikube", "Ready", "master", "v1.15.2", "4.15.0", "192.168.64.107", "<none>", "0", "10", "10", "0", "0", "4000", "7874"}
|
||||
e := render.Fields{"minikube", "Ready", "master", "v1.15.2", "4.15.0", "192.168.64.107", "<none>", "0", "10m", "10Mi", "0", "0", "4,000m", "7,874Mi"}
|
||||
assert.Equal(t, e, r.Fields[:14])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ func TestPodDisruptionBudgetRender(t *testing.T) {
|
|||
c.Render(load(t, "pdb"), "", &r)
|
||||
|
||||
assert.Equal(t, "default/fred", r.ID)
|
||||
assert.Equal(t, render.Fields{"default", "fred", "2", "n/a", "0", "0", "2", "0"}, r.Fields[:8])
|
||||
assert.Equal(t, render.Fields{"default", "fred", "2", render.NAValue, "0", "0", "2", "0"}, r.Fields[:8])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ func (Pod) Header(ns string) Header {
|
|||
HeaderColumn{Name: "READY"},
|
||||
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||
HeaderColumn{Name: "STATUS"},
|
||||
HeaderColumn{Name: "CPU(R:L)", Align: tview.AlignRight, MX: true, Wide: true},
|
||||
HeaderColumn{Name: "MEM(R:L)", Align: tview.AlignRight, MX: true, Wide: true},
|
||||
HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
||||
HeaderColumn{Name: "%CPU/R", Align: tview.AlignRight, MX: true},
|
||||
|
|
@ -92,7 +94,7 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error {
|
|||
|
||||
ss := po.Status.ContainerStatuses
|
||||
cr, _, rc := p.Statuses(ss)
|
||||
c, perc := p.gatherPodMX(&po, pwm.MX)
|
||||
c, perc, res := p.gatherPodMX(&po, pwm.MX)
|
||||
phase := p.Phase(&po)
|
||||
r.ID = client.MetaFQN(po.ObjectMeta)
|
||||
r.Fields = Fields{
|
||||
|
|
@ -102,6 +104,8 @@ func (p Pod) Render(o interface{}, ns string, r *Row) error {
|
|||
strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss)),
|
||||
strconv.Itoa(rc),
|
||||
phase,
|
||||
ToResourcesMc(res),
|
||||
ToResourcesMi(res),
|
||||
c.cpu,
|
||||
c.mem,
|
||||
perc.cpu,
|
||||
|
|
@ -149,7 +153,19 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object {
|
|||
return p
|
||||
}
|
||||
|
||||
func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) {
|
||||
const (
|
||||
requestCPU qualifiedResource = "rcpu"
|
||||
requestMEM = "rmem"
|
||||
limitCPU = "lcpu"
|
||||
limitMEM = "lmem"
|
||||
)
|
||||
|
||||
type (
|
||||
qualifiedResource string
|
||||
resources map[qualifiedResource]*resource.Quantity
|
||||
)
|
||||
|
||||
func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric, r resources) {
|
||||
c, p = noMetric(), noMetric()
|
||||
if mx == nil {
|
||||
return
|
||||
|
|
@ -161,12 +177,15 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) {
|
|||
}
|
||||
cpu, mem := currentRes(mx)
|
||||
c = metric{
|
||||
cpu: ToMillicore(cpu.MilliValue()),
|
||||
cpu: ToMc(cpu.MilliValue()),
|
||||
mem: ToMi(client.ToMB(mem.Value())),
|
||||
}
|
||||
|
||||
rc, rm := resourceRequests(pod.Spec.Containers)
|
||||
lc, lm := resourceLimits(pod.Spec.Containers)
|
||||
rc, rm := podRequests(pod.Spec)
|
||||
lc, lm := podLimits(pod.Spec)
|
||||
r = make(resources, 4)
|
||||
r[requestCPU], r[requestMEM] = rc, rm
|
||||
r[limitCPU], r[limitMEM] = lc, lm
|
||||
p = metric{
|
||||
cpu: client.ToPercentageStr(cpu.MilliValue(), rc.MilliValue()),
|
||||
mem: client.ToPercentageStr(client.ToMB(mem.Value()), client.ToMB(rm.Value())),
|
||||
|
|
@ -197,7 +216,8 @@ func containerLimits(co v1.Container) (cpu, mem *resource.Quantity) {
|
|||
return limit.Cpu(), limit.Memory()
|
||||
}
|
||||
|
||||
func resourceLimits(cc []v1.Container) (cpu, mem resource.Quantity) {
|
||||
func resourceLimits(cc []v1.Container) (cpu, mem *resource.Quantity) {
|
||||
cpu, mem = new(resource.Quantity), new(resource.Quantity)
|
||||
for _, co := range cc {
|
||||
limit := co.Resources.Limits
|
||||
if len(limit) == 0 {
|
||||
|
|
@ -215,7 +235,28 @@ func resourceLimits(cc []v1.Container) (cpu, mem resource.Quantity) {
|
|||
return
|
||||
}
|
||||
|
||||
func resourceRequests(cc []v1.Container) (cpu, mem resource.Quantity) {
|
||||
func podLimits(spec v1.PodSpec) (*resource.Quantity, *resource.Quantity) {
|
||||
cc, cm := resourceLimits(spec.Containers)
|
||||
ic, im := resourceLimits(spec.InitContainers)
|
||||
|
||||
cc.Add(*ic)
|
||||
cm.Add(*im)
|
||||
|
||||
return cc, cm
|
||||
}
|
||||
|
||||
func podRequests(spec v1.PodSpec) (*resource.Quantity, *resource.Quantity) {
|
||||
cc, cm := resourceRequests(spec.Containers)
|
||||
ic, im := resourceRequests(spec.InitContainers)
|
||||
|
||||
cc.Add(*ic)
|
||||
cm.Add(*im)
|
||||
|
||||
return cc, cm
|
||||
}
|
||||
|
||||
func resourceRequests(cc []v1.Container) (cpu, mem *resource.Quantity) {
|
||||
cpu, mem = new(resource.Quantity), new(resource.Quantity)
|
||||
for _, co := range cc {
|
||||
c, m := containerResources(co)
|
||||
if c == nil || m == nil {
|
||||
|
|
@ -230,6 +271,7 @@ func resourceRequests(cc []v1.Container) (cpu, mem resource.Quantity) {
|
|||
mem.Add(*m)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -159,8 +159,8 @@ func TestPodRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Running", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:15])
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Running", "100m/-", "70Mi/170Mi", "10m", "10Mi", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:17])
|
||||
}
|
||||
|
||||
func BenchmarkPodRender(b *testing.B) {
|
||||
|
|
@ -190,8 +190,8 @@ func TestPodInitRender(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, "default/nginx", r.ID)
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Init:0/1", "10", "10", "10", "14", render.NAValue, "5", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:15])
|
||||
e := render.Fields{"default", "nginx", "●", "1/1", "0", "Init:0/1", "200m/-", "140Mi/340Mi", "10m", "10Mi", "5", "7", render.NAValue, "2", "172.17.0.6", "minikube", "BE"}
|
||||
assert.Equal(t, e, r.Fields[:17])
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -40,4 +40,7 @@ const (
|
|||
|
||||
// UnknownValue represents an unknown.
|
||||
UnknownValue = "<unknown>"
|
||||
|
||||
// UnsetValue represent an unset value
|
||||
UnsetValue = ""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -86,6 +86,9 @@ func (a *App) SetRunning(f bool) {
|
|||
a.running = f
|
||||
}
|
||||
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (a *App) BufferCompleted(s string) {}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (a *App) BufferChanged(s string) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func Pad(s string, width int) string {
|
|||
func toAgeHuman(s string) string {
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return "n/a"
|
||||
return render.NAValue
|
||||
}
|
||||
|
||||
return duration.HumanDuration(d)
|
||||
|
|
|
|||
|
|
@ -203,6 +203,9 @@ func (p *Prompt) write(text, suggest string) {
|
|||
// ----------------------------------------------------------------------------
|
||||
// Event Listener protocol...
|
||||
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (p *Prompt) BufferCompleted(s string) {}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (p *Prompt) BufferChanged(s string) {
|
||||
p.update(s)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@ func TestCmdNew(t *testing.T) {
|
|||
model := model.NewFishBuff(':', model.CommandBuffer)
|
||||
v.SetModel(model)
|
||||
model.AddListener(v)
|
||||
model.SetText("blee")
|
||||
for _, r := range "blee" {
|
||||
model.Add(r)
|
||||
}
|
||||
|
||||
assert.Equal(t, "\x00> [::b]blee\n", v.GetText(false))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func NewAlias(gvr client.GVR) ResourceViewer {
|
|||
a.GetTable().SetColorerFn(render.Alias{}.ColorerFunc())
|
||||
a.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
||||
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
||||
a.SetBindKeysFn(a.bindKeys)
|
||||
a.AddBindKeysFn(a.bindKeys)
|
||||
a.SetContextFn(a.aliasContext)
|
||||
|
||||
return &a
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ func TestAliasSearch(t *testing.T) {
|
|||
v.App().Prompt().SendStrokes("blee")
|
||||
|
||||
assert.Equal(t, 3, v.GetTable().GetColumnCount())
|
||||
time.Sleep(1_000 * time.Millisecond)
|
||||
assert.Equal(t, 2, v.GetTable().GetRowCount())
|
||||
}
|
||||
|
||||
|
|
@ -62,6 +63,8 @@ type buffL struct {
|
|||
func (b *buffL) BufferChanged(s string) {
|
||||
b.changed++
|
||||
}
|
||||
func (b *buffL) BufferCompleted(s string) {}
|
||||
|
||||
func (b *buffL) BufferActive(state bool, kind model.BufferKind) {
|
||||
b.active++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -139,13 +139,11 @@ func (a *App) layout(ctx context.Context, version string) {
|
|||
|
||||
func (a *App) initSignals() {
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, syscall.SIGTERM, syscall.SIGABRT, syscall.SIGINT, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
signal.Notify(sig, syscall.SIGHUP)
|
||||
|
||||
go func(sig chan os.Signal) {
|
||||
s := <-sig
|
||||
if s == syscall.SIGHUP {
|
||||
os.Exit(0)
|
||||
}
|
||||
<-sig
|
||||
os.Exit(0)
|
||||
}(sig)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,9 +61,9 @@ func (b *Browser) Init(ctx context.Context) error {
|
|||
b.app.CmdBuff().Reset()
|
||||
}
|
||||
|
||||
b.bindKeys()
|
||||
if b.bindKeysFn != nil {
|
||||
b.bindKeysFn(b.Actions())
|
||||
b.bindKeys(b.Actions())
|
||||
for _, f := range b.bindKeysFn {
|
||||
f(b.Actions())
|
||||
}
|
||||
b.accessor, err = dao.AccessorFor(b.app.factory, b.GVR())
|
||||
if err != nil {
|
||||
|
|
@ -104,8 +104,8 @@ func (b *Browser) suggestFilter() model.SuggestionFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *Browser) bindKeys() {
|
||||
b.Actions().Add(ui.KeyActions{
|
||||
func (b *Browser) bindKeys(aa ui.KeyActions) {
|
||||
aa.Add(ui.KeyActions{
|
||||
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", b.resetCmd, false),
|
||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", b.filterCmd, false),
|
||||
})
|
||||
|
|
@ -132,7 +132,6 @@ func (b *Browser) Start() {
|
|||
|
||||
// Stop terminates browser updates.
|
||||
func (b *Browser) Stop() {
|
||||
log.Debug().Msgf("BRO-STOP %v", b.GVR())
|
||||
if b.cancelFn != nil {
|
||||
b.cancelFn()
|
||||
b.cancelFn = nil
|
||||
|
|
@ -143,7 +142,10 @@ func (b *Browser) Stop() {
|
|||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (b *Browser) BufferChanged(s string) {
|
||||
func (b *Browser) BufferChanged(s string) {}
|
||||
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (b *Browser) BufferCompleted(s string) {
|
||||
if ui.IsLabelSelector(s) {
|
||||
b.GetModel().SetLabelFilter(ui.TrimLabelSelector(s))
|
||||
} else {
|
||||
|
|
@ -437,7 +439,7 @@ func (b *Browser) refreshActions() {
|
|||
|
||||
if b.app.ConOK() {
|
||||
b.namespaceActions(aa)
|
||||
if !b.app.Config.K9s.GetReadOnly() {
|
||||
if !b.app.Config.K9s.IsReadOnly() {
|
||||
if client.Can(b.meta.Verbs, "edit") {
|
||||
aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true)
|
||||
}
|
||||
|
|
@ -454,11 +456,10 @@ func (b *Browser) refreshActions() {
|
|||
|
||||
pluginActions(b, aa)
|
||||
hotKeyActions(b, aa)
|
||||
b.Actions().Add(aa)
|
||||
|
||||
if b.bindKeysFn != nil {
|
||||
b.bindKeysFn(b.Actions())
|
||||
for _, f := range b.bindKeysFn {
|
||||
f(aa)
|
||||
}
|
||||
b.Actions().Add(aa)
|
||||
b.app.Menu().HydrateMenu(b.Hints())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func NewConfigMap(gvr client.GVR) ResourceViewer {
|
|||
s := ConfigMap{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
s.SetBindKeysFn(s.bindKeys)
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
|
||||
return &s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func NewContainer(gvr client.GVR) ResourceViewer {
|
|||
c.GetTable().SetEnterFn(c.viewLogs)
|
||||
c.GetTable().SetColorerFn(render.Container{}.ColorerFunc())
|
||||
c.GetTable().SetDecorateFn(c.decorateRows)
|
||||
c.SetBindKeysFn(c.bindKeys)
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
c.GetTable().SetDecorateFn(c.portForwardIndicator)
|
||||
|
||||
return &c
|
||||
|
|
@ -66,7 +66,7 @@ func (c *Container) bindDangerousKeys(aa ui.KeyActions) {
|
|||
func (c *Container) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
|
||||
if !c.App().Config.K9s.GetReadOnly() {
|
||||
if !c.App().Config.K9s.IsReadOnly() {
|
||||
c.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func NewContext(gvr client.GVR) ResourceViewer {
|
|||
}
|
||||
c.GetTable().SetEnterFn(c.useCtx)
|
||||
c.GetTable().SetColorerFn(render.Context{}.ColorerFunc())
|
||||
c.SetBindKeysFn(c.bindKeys)
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
|
||||
return &c
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type CronJob struct {
|
|||
// NewCronJob returns a new viewer.
|
||||
func NewCronJob(gvr client.GVR) ResourceViewer {
|
||||
c := CronJob{ResourceViewer: NewBrowser(gvr)}
|
||||
c.SetBindKeysFn(c.bindKeys)
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
c.GetTable().SetEnterFn(c.showJobs)
|
||||
c.GetTable().SetColorerFn(render.CronJob{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,10 @@ func (d *Details) TextFiltered(lines []string, matches fuzzy.Matches) {
|
|||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (d *Details) BufferChanged(s string) {
|
||||
func (d *Details) BufferChanged(s string) {}
|
||||
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (d *Details) BufferCompleted(s string) {
|
||||
d.model.Filter(s)
|
||||
d.updateTitle()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,20 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
kustomize = "kustomization"
|
||||
kustomizeNoExt = "Kustomization"
|
||||
kustomizeYAML = kustomize + extYAML
|
||||
kustomizeYML = kustomize + extYML
|
||||
extYAML = ".yaml"
|
||||
extYML = ".yml"
|
||||
)
|
||||
|
||||
// Dir represents a command directory view.
|
||||
|
|
@ -31,7 +40,7 @@ func NewDir(path string) ResourceViewer {
|
|||
}
|
||||
d.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
||||
d.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
||||
d.SetBindKeysFn(d.bindKeys)
|
||||
d.AddBindKeysFn(d.bindKeys)
|
||||
d.SetContextFn(d.dirContext)
|
||||
d.GetTable().SetColorerFn(render.Dir{}.ColorerFunc())
|
||||
|
||||
|
|
@ -51,13 +60,21 @@ func (d *Dir) dirContext(ctx context.Context) context.Context {
|
|||
return context.WithValue(ctx, internal.KeyPath, d.path)
|
||||
}
|
||||
|
||||
func (d *Dir) bindDangerousKeys(aa ui.KeyActions) {
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyA: ui.NewKeyAction("Apply", d.applyCmd, true),
|
||||
ui.KeyD: ui.NewKeyAction("Delete", d.delCmd, true),
|
||||
ui.KeyE: ui.NewKeyAction("Edit", d.editCmd, true),
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Dir) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
|
||||
if !d.App().Config.K9s.IsReadOnly() {
|
||||
d.bindDangerousKeys(aa)
|
||||
}
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyA: ui.NewKeyAction("Apply", d.applyCmd, true),
|
||||
ui.KeyD: ui.NewKeyAction("Delete", d.delCmd, true),
|
||||
ui.KeyE: ui.NewKeyAction("Edit", d.editCmd, true),
|
||||
ui.KeyY: ui.NewKeyAction("YAML", d.viewCmd, true),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true),
|
||||
})
|
||||
|
|
@ -98,7 +115,6 @@ func (d *Dir) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Selected %q", sel)
|
||||
if !isManifest(sel) {
|
||||
d.App().Flash().Errf("you must select a manifest")
|
||||
return nil
|
||||
|
|
@ -136,18 +152,62 @@ func (d *Dir) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
func isKustomized(sel string) bool {
|
||||
if isManifest(sel) {
|
||||
return false
|
||||
}
|
||||
|
||||
ff, err := ioutil.ReadDir(sel)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
kk := []string{kustomizeNoExt, kustomizeYAML, kustomizeYML}
|
||||
for _, f := range ff {
|
||||
if config.InList(kk, f.Name()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func containsDir(sel string) bool {
|
||||
if isManifest(sel) {
|
||||
return false
|
||||
}
|
||||
|
||||
ff, err := ioutil.ReadDir(sel)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, f := range ff {
|
||||
if f.IsDir() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
sel := d.GetTable().GetSelectedItem()
|
||||
if sel == "" {
|
||||
return evt
|
||||
}
|
||||
|
||||
opts := []string{"-f"}
|
||||
if containsDir(sel) {
|
||||
opts = append(opts, "-R")
|
||||
}
|
||||
if isKustomized(sel) {
|
||||
opts = []string{"-k"}
|
||||
}
|
||||
d.Stop()
|
||||
defer d.Start()
|
||||
{
|
||||
args := make([]string, 0, 10)
|
||||
args = append(args, "apply")
|
||||
args = append(args, "-f")
|
||||
args = append(args, opts...)
|
||||
args = append(args, sel)
|
||||
res, err := runKu(d.App(), shellOpts{clear: false, args: args})
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsManifest(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
file string
|
||||
e bool
|
||||
}{
|
||||
"yaml": {file: "fred.yaml", e: true},
|
||||
"yml": {file: "fred.yml", e: true},
|
||||
"nope": {file: "fred.txt"},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, isManifest(u.file))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsKustomized(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
path string
|
||||
e bool
|
||||
}{
|
||||
"toast": {path: "testdata/fred"},
|
||||
"yaml": {path: "testdata/kmanifests", e: true},
|
||||
"yml": {path: "testdata/k1manifests", e: true},
|
||||
"noExt": {path: "testdata/k2manifests", e: true},
|
||||
}
|
||||
|
||||
for k := range uu {
|
||||
u := uu[k]
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, isKustomized(u.path))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ func NewDeploy(gvr client.GVR) ResourceViewer {
|
|||
ResourceViewer: NewPortForwardExtender(
|
||||
NewRestartExtender(
|
||||
NewScaleExtender(
|
||||
NewSetImageExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(
|
||||
NewBrowser(gvr),
|
||||
nil,
|
||||
|
|
@ -31,7 +31,7 @@ func NewDeploy(gvr client.GVR) ResourceViewer {
|
|||
),
|
||||
),
|
||||
}
|
||||
d.SetBindKeysFn(d.bindKeys)
|
||||
d.AddBindKeysFn(d.bindKeys)
|
||||
d.GetTable().SetEnterFn(d.showPods)
|
||||
d.GetTable().SetColorerFn(render.Deployment{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
|
|
@ -13,5 +14,6 @@ func TestDeploy(t *testing.T) {
|
|||
|
||||
assert.Nil(t, v.Init(makeCtx()))
|
||||
assert.Equal(t, "Deployments", v.Name())
|
||||
assert.Equal(t, 13, len(v.Hints()))
|
||||
fmt.Println(v.Hints())
|
||||
assert.Equal(t, 14, len(v.Hints()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ func NewDaemonSet(gvr client.GVR) ResourceViewer {
|
|||
d := DaemonSet{
|
||||
ResourceViewer: NewPortForwardExtender(
|
||||
NewRestartExtender(
|
||||
NewSetImageExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), nil),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
d.SetBindKeysFn(d.bindKeys)
|
||||
d.AddBindKeysFn(d.bindKeys)
|
||||
d.GetTable().SetEnterFn(d.showPods)
|
||||
d.GetTable().SetColorerFn(render.DaemonSet{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -13,5 +13,5 @@ func TestDaemonSet(t *testing.T) {
|
|||
|
||||
assert.Nil(t, v.Init(makeCtx()))
|
||||
assert.Equal(t, "DaemonSets", v.Name())
|
||||
assert.Equal(t, 14, len(v.Hints()))
|
||||
assert.Equal(t, 15, len(v.Hints()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ func NewEvent(gvr client.GVR) ResourceViewer {
|
|||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
e.GetTable().SetColorerFn(render.Event{}.ColorerFunc())
|
||||
e.SetBindKeysFn(e.bindKeys)
|
||||
e.AddBindKeysFn(e.bindKeys)
|
||||
e.GetTable().SetSortCol(ageCol, true)
|
||||
|
||||
return &e
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ func execute(opts shellOpts) error {
|
|||
}()
|
||||
|
||||
log.Debug().Msgf("Running command> %s %s", opts.binary, strings.Join(opts.args, " "))
|
||||
cmd := exec.Command(opts.binary, opts.args...)
|
||||
cmd := exec.CommandContext(ctx, opts.binary, opts.args...)
|
||||
|
||||
var err error
|
||||
if opts.background {
|
||||
|
|
@ -162,7 +162,6 @@ func oneShoot(opts shellOpts) (string, error) {
|
|||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, buff, buff
|
||||
_, _ = cmd.Stdout.Write([]byte(opts.banner))
|
||||
err = cmd.Run()
|
||||
log.Debug().Msgf("RES %q", buff)
|
||||
|
||||
return strings.Trim(buff.String(), "\n"), err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type Group struct {
|
|||
func NewGroup(gvr client.GVR) ResourceViewer {
|
||||
g := Group{ResourceViewer: NewBrowser(gvr)}
|
||||
g.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
||||
g.SetBindKeysFn(g.bindKeys)
|
||||
g.AddBindKeysFn(g.bindKeys)
|
||||
g.SetContextFn(g.subjectCtx)
|
||||
|
||||
return &g
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func NewHelm(gvr client.GVR) ResourceViewer {
|
|||
c.GetTable().SetColorerFn(render.Helm{}.ColorerFunc())
|
||||
c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
c.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||
c.SetBindKeysFn(c.bindKeys)
|
||||
c.AddBindKeysFn(c.bindKeys)
|
||||
c.SetContextFn(c.chartContext)
|
||||
|
||||
return &c
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ func TestK8sEnv(t *testing.T) {
|
|||
assert.Equal(t, cl, env["CLUSTER"])
|
||||
assert.Equal(t, ctx, env["CONTEXT"])
|
||||
assert.Equal(t, u, env["USER"])
|
||||
assert.Equal(t, "n/a", env["GROUPS"])
|
||||
assert.Equal(t, render.NAValue, env["GROUPS"])
|
||||
assert.Equal(t, cfg, env["KUBECONFIG"])
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ func TestK9sEnv(t *testing.T) {
|
|||
assert.Equal(t, cl, env["CLUSTER"])
|
||||
assert.Equal(t, ctx, env["CONTEXT"])
|
||||
assert.Equal(t, u, env["USER"])
|
||||
assert.Equal(t, "n/a", env["GROUPS"])
|
||||
assert.Equal(t, render.NAValue, env["GROUPS"])
|
||||
assert.Equal(t, cfg, env["KUBECONFIG"])
|
||||
assert.Equal(t, "fred", env["NAMESPACE"])
|
||||
assert.Equal(t, "blee", env["NAME"])
|
||||
|
|
|
|||
|
|
@ -3,21 +3,17 @@ package view
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const setImageKey = "setImage"
|
||||
|
||||
// SetImageExtender adds set image extensions
|
||||
type SetImageExtender struct {
|
||||
ResourceViewer
|
||||
}
|
||||
const imageKey = "setImage"
|
||||
|
||||
type imageFormSpec struct {
|
||||
name, dockerImage, newDockerImage string
|
||||
|
|
@ -44,20 +40,28 @@ func (m *imageFormSpec) imageSpec() dao.ImageSpec {
|
|||
return ret
|
||||
}
|
||||
|
||||
func NewSetImageExtender(r ResourceViewer) ResourceViewer {
|
||||
s := SetImageExtender{ResourceViewer: r}
|
||||
s.bindKeys(s.Actions())
|
||||
// ImageExtender provides for overriding container images.
|
||||
type ImageExtender struct {
|
||||
ResourceViewer
|
||||
}
|
||||
|
||||
func NewImageExtender(r ResourceViewer) ResourceViewer {
|
||||
s := ImageExtender{ResourceViewer: r}
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) bindKeys(aa ui.KeyActions) {
|
||||
func (s *ImageExtender) bindKeys(aa ui.KeyActions) {
|
||||
if s.App().Config.K9s.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyI: ui.NewKeyAction("SetImage", s.setImageCmd, true),
|
||||
ui.KeyI: ui.NewKeyAction("Set Image", s.setImageCmd, false),
|
||||
})
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (s *ImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
path := s.GetTable().GetSelectedItem()
|
||||
if path == "" {
|
||||
return nil
|
||||
|
|
@ -65,63 +69,58 @@ func (s *SetImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
s.Stop()
|
||||
defer s.Start()
|
||||
s.showSetImageDialog(path)
|
||||
s.showImageDialog(path)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) showSetImageDialog(path string) {
|
||||
func (s *ImageExtender) showImageDialog(path string) {
|
||||
confirm := tview.NewModalForm("<Set image>", s.makeSetImageForm(path))
|
||||
confirm.SetText(fmt.Sprintf("Set image %s %s", s.GVR(), path))
|
||||
confirm.SetDoneFunc(func(int, string) {
|
||||
s.dismissDialog()
|
||||
})
|
||||
s.App().Content.AddPage(setImageKey, confirm, false, false)
|
||||
s.App().Content.ShowPage(setImageKey)
|
||||
s.App().Content.AddPage(imageKey, confirm, false, false)
|
||||
s.App().Content.ShowPage(imageKey)
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) makeSetImageForm(sel string) *tview.Form {
|
||||
func (s *ImageExtender) makeSetImageForm(sel string) *tview.Form {
|
||||
f := s.makeStyledForm()
|
||||
podSpec, err := s.getPodSpec(sel)
|
||||
if err != nil {
|
||||
s.App().Flash().Err(err)
|
||||
return nil
|
||||
}
|
||||
var formContainerLines []imageFormSpec
|
||||
formContainerLines := make([]*imageFormSpec, len(podSpec.InitContainers)+len(podSpec.Containers))
|
||||
for _, spec := range podSpec.InitContainers {
|
||||
formContainerLines = append(formContainerLines, imageFormSpec{init: true, name: spec.Name, dockerImage: spec.Image})
|
||||
formContainerLines = append(formContainerLines, &imageFormSpec{init: true, name: spec.Name, dockerImage: spec.Image})
|
||||
}
|
||||
for _, spec := range podSpec.Containers {
|
||||
formContainerLines = append(formContainerLines, imageFormSpec{init: false, name: spec.Name, dockerImage: spec.Image})
|
||||
formContainerLines = append(formContainerLines, &imageFormSpec{name: spec.Name, dockerImage: spec.Image})
|
||||
}
|
||||
for _, ctn := range formContainerLines {
|
||||
ctnCopy := ctn
|
||||
for i := range formContainerLines {
|
||||
ctn := formContainerLines[i]
|
||||
f.AddInputField(ctn.name, ctn.dockerImage, 0, nil, func(changed string) {
|
||||
ctnCopy.newDockerImage = changed
|
||||
ctn.newDockerImage = changed
|
||||
})
|
||||
}
|
||||
|
||||
f.AddButton("OK", func() {
|
||||
defer s.dismissDialog()
|
||||
if err != nil {
|
||||
s.App().Flash().Err(err)
|
||||
return
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
var imageSpecsModified dao.ImageSpecs
|
||||
for _, v := range formContainerLines {
|
||||
if v.modified() {
|
||||
imageSpecsModified = append(imageSpecsModified, v.imageSpec())
|
||||
}
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.App().Conn().Config().CallTimeout())
|
||||
defer cancel()
|
||||
if err := s.setImages(ctx, sel, imageSpecsModified); err != nil {
|
||||
log.Error().Err(err).Msgf("PodSpec %s image update failed", sel)
|
||||
s.App().Flash().Err(err)
|
||||
} else {
|
||||
s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel)
|
||||
return
|
||||
}
|
||||
s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel)
|
||||
})
|
||||
f.AddButton("Cancel", func() {
|
||||
s.dismissDialog()
|
||||
|
|
@ -129,11 +128,11 @@ func (s *SetImageExtender) makeSetImageForm(sel string) *tview.Form {
|
|||
return f
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) dismissDialog() {
|
||||
s.App().Content.RemovePage(setImageKey)
|
||||
func (s *ImageExtender) dismissDialog() {
|
||||
s.App().Content.RemovePage(imageKey)
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) makeStyledForm() *tview.Form {
|
||||
func (s *ImageExtender) makeStyledForm() *tview.Form {
|
||||
f := tview.NewForm()
|
||||
f.SetItemPadding(0)
|
||||
f.SetButtonsAlign(tview.AlignCenter).
|
||||
|
|
@ -144,19 +143,20 @@ func (s *SetImageExtender) makeStyledForm() *tview.Form {
|
|||
return f
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) getPodSpec(path string) (*corev1.PodSpec, error) {
|
||||
func (s *ImageExtender) getPodSpec(path string) (*corev1.PodSpec, error) {
|
||||
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expecting a resourceWPodSpec resource for %q", s.GVR())
|
||||
return nil, fmt.Errorf("expecting a ContainsPodSpec for %q but got %T", s.GVR(), res)
|
||||
}
|
||||
|
||||
return resourceWPodSpec.GetPodSpec(path)
|
||||
}
|
||||
|
||||
func (s *SetImageExtender) setImages(ctx context.Context, path string, imageSpecs dao.ImageSpecs) error {
|
||||
func (s *ImageExtender) setImages(ctx context.Context, path string, imageSpecs dao.ImageSpecs) error {
|
||||
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -54,7 +54,7 @@ func NewLog(gvr client.GVR, path, co string, prev bool) *Log {
|
|||
Flex: tview.NewFlex(),
|
||||
model: model.NewLog(
|
||||
gvr,
|
||||
buildLogOpts(path, co, prev, true, config.DefaultLoggerTailCount),
|
||||
buildLogOpts(path, co, prev, false, config.DefaultLoggerTailCount),
|
||||
flushTimeout,
|
||||
),
|
||||
}
|
||||
|
|
@ -105,7 +105,7 @@ func (l *Log) Init(ctx context.Context) (err error) {
|
|||
func (l *Log) LogCleared() {
|
||||
l.app.QueueUpdateDraw(func() {
|
||||
l.logs.Clear()
|
||||
l.logs.ScrollTo(0, 0)
|
||||
// l.logs.ScrollTo(0, 0)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -123,18 +123,21 @@ func (l *Log) LogFailed(err error) {
|
|||
}
|
||||
|
||||
// LogChanged updates the logs.
|
||||
func (l *Log) LogChanged(lines dao.LogItems) {
|
||||
func (l *Log) LogChanged(lines [][]byte) {
|
||||
l.app.QueueUpdateDraw(func() {
|
||||
l.Flush(lines)
|
||||
})
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (l *Log) BufferChanged(s string) {
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (l *Log) BufferCompleted(s string) {
|
||||
l.model.Filter(l.logs.cmdBuff.GetText())
|
||||
l.updateTitle()
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (l *Log) BufferChanged(s string) {}
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
func (l *Log) BufferActive(state bool, k model.BufferKind) {
|
||||
l.app.BufferActive(state, k)
|
||||
|
|
@ -181,24 +184,40 @@ func (l *Log) Name() string { return logTitle }
|
|||
|
||||
func (l *Log) bindKeys() {
|
||||
l.logs.Actions().Set(ui.KeyActions{
|
||||
ui.Key0: ui.NewKeyAction("all", l.sinceCmd(-1), true),
|
||||
ui.Key1: ui.NewKeyAction("1m", l.sinceCmd(60), true),
|
||||
ui.Key2: ui.NewKeyAction("5m", l.sinceCmd(5*60), true),
|
||||
ui.Key3: ui.NewKeyAction("15m", l.sinceCmd(15*60), true),
|
||||
ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true),
|
||||
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
||||
tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
|
||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
||||
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
||||
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
||||
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.toggleTextWrapCmd, true),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
||||
ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, true),
|
||||
ui.Key0: ui.NewKeyAction("all", l.sinceCmd(-1), true),
|
||||
ui.Key1: ui.NewKeyAction("1m", l.sinceCmd(60), true),
|
||||
ui.Key2: ui.NewKeyAction("5m", l.sinceCmd(5*60), true),
|
||||
ui.Key3: ui.NewKeyAction("15m", l.sinceCmd(15*60), true),
|
||||
ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true),
|
||||
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, false),
|
||||
tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
|
||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
||||
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
||||
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
||||
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.toggleTextWrapCmd, true),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
||||
ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, true),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *Log) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !l.logs.cmdBuff.IsActive() {
|
||||
if l.logs.cmdBuff.GetText() == "" {
|
||||
return l.app.PrevCmd(evt)
|
||||
}
|
||||
}
|
||||
|
||||
l.logs.cmdBuff.Reset()
|
||||
l.logs.cmdBuff.SetActive(false)
|
||||
l.model.Filter(l.logs.cmdBuff.GetText())
|
||||
l.updateTitle()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendStrokes (testing only!)
|
||||
func (l *Log) SendStrokes(s string) {
|
||||
l.app.Prompt().SendStrokes(s)
|
||||
|
|
@ -248,14 +267,13 @@ func (l *Log) Logs() *Details {
|
|||
var EOL = []byte{'\n'}
|
||||
|
||||
// Flush write logs to viewer.
|
||||
func (l *Log) Flush(lines dao.LogItems) {
|
||||
func (l *Log) Flush(lines [][]byte) {
|
||||
log.Debug().Msgf("LOG-FLUSH %d", len(lines))
|
||||
if !l.indicator.AutoScroll() {
|
||||
return
|
||||
}
|
||||
ll := make([][]byte, len(lines))
|
||||
lines.Render(l.Indicator().showTime, ll)
|
||||
_, _ = l.ansiWriter.Write(EOL)
|
||||
if _, err := l.ansiWriter.Write(bytes.Join(ll, EOL)); err != nil {
|
||||
if _, err := l.ansiWriter.Write(bytes.Join(lines, EOL)); err != nil {
|
||||
log.Error().Err(err).Msgf("write logs failed")
|
||||
}
|
||||
l.logs.ScrollToEnd()
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func TestLogViewClear(t *testing.T) {
|
|||
func TestLogTimestamp(t *testing.T) {
|
||||
l := NewLog(client.NewGVR("test"), "fred/blee", "c1", false)
|
||||
l.Init(makeContext())
|
||||
buff := dao.LogItems{
|
||||
ii := dao.LogItems{
|
||||
&dao.LogItem{
|
||||
Pod: "fred/blee",
|
||||
Container: "c1",
|
||||
|
|
@ -61,10 +61,10 @@ func TestLogTimestamp(t *testing.T) {
|
|||
}
|
||||
var list logList
|
||||
l.GetModel().AddListener(&list)
|
||||
l.GetModel().Set(buff)
|
||||
l.GetModel().Set(ii)
|
||||
l.SendKeys(ui.KeyT)
|
||||
l.Logs().Clear()
|
||||
l.Flush(buff)
|
||||
l.Flush(ii.Lines(true))
|
||||
|
||||
assert.Equal(t, fmt.Sprintf("\n%-30s %s", "ttt", "fred/blee:c1 Testing 1, 2, 3"), l.Logs().GetText(true))
|
||||
assert.Equal(t, 2, list.change)
|
||||
|
|
@ -99,11 +99,11 @@ type logList struct {
|
|||
lines string
|
||||
}
|
||||
|
||||
func (l *logList) LogChanged(ii dao.LogItems) {
|
||||
func (l *logList) LogChanged(ll [][]byte) {
|
||||
l.change++
|
||||
l.lines = ""
|
||||
for _, i := range ii {
|
||||
l.lines += string(i.Render(0, false))
|
||||
for _, line := range ll {
|
||||
l.lines += string(line)
|
||||
}
|
||||
}
|
||||
func (l *logList) LogCleared() { l.clear++ }
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func TestLog(t *testing.T) {
|
|||
v.Flush(dao.LogItems{
|
||||
dao.NewLogItemFromString("blee"),
|
||||
dao.NewLogItemFromString("bozo"),
|
||||
})
|
||||
}.Lines(false))
|
||||
|
||||
assert.Equal(t, 29, len(v.Logs().GetText(true)))
|
||||
}
|
||||
|
|
@ -38,7 +38,7 @@ func BenchmarkLogFlush(b *testing.B) {
|
|||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
v.Flush(items)
|
||||
v.Flush(items.Lines(false))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -61,7 +61,10 @@ func TestLogViewSave(t *testing.T) {
|
|||
v.Init(makeContext())
|
||||
|
||||
app := makeApp()
|
||||
v.Flush(dao.LogItems{dao.NewLogItemFromString("blee"), dao.NewLogItemFromString("bozo")})
|
||||
v.Flush(dao.LogItems{
|
||||
dao.NewLogItemFromString("blee"),
|
||||
dao.NewLogItemFromString("bozo"),
|
||||
}.Lines(false))
|
||||
config.K9sDumpDir = "/tmp"
|
||||
dir := filepath.Join(config.K9sDumpDir, app.Config.K9s.CurrentCluster)
|
||||
c1, _ := ioutil.ReadDir(dir)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func NewLogsExtender(v ResourceViewer, f ContainerFunc) ResourceViewer {
|
|||
ResourceViewer: v,
|
||||
containerFn: f,
|
||||
}
|
||||
l.bindKeys(l.Actions())
|
||||
l.AddBindKeysFn(l.bindKeys)
|
||||
|
||||
return &l
|
||||
}
|
||||
|
|
@ -27,8 +27,8 @@ func NewLogsExtender(v ResourceViewer, f ContainerFunc) ResourceViewer {
|
|||
// BindKeys injects new menu actions.
|
||||
func (l *LogsExtender) bindKeys(aa ui.KeyActions) {
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true),
|
||||
ui.KeyShiftL: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true),
|
||||
ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true),
|
||||
ui.KeyP: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func NewNode(gvr client.GVR) ResourceViewer {
|
|||
n := Node{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
n.SetBindKeysFn(n.bindKeys)
|
||||
n.AddBindKeysFn(n.bindKeys)
|
||||
n.GetTable().SetEnterFn(n.showPods)
|
||||
|
||||
return &n
|
||||
|
|
@ -49,7 +49,7 @@ func (n *Node) bindDangerousKeys(aa ui.KeyActions) {
|
|||
func (n *Node) bindKeys(aa ui.KeyActions) {
|
||||
aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlD)
|
||||
|
||||
if !n.App().Config.K9s.GetReadOnly() {
|
||||
if !n.App().Config.K9s.IsReadOnly() {
|
||||
n.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func NewNamespace(gvr client.GVR) ResourceViewer {
|
|||
n.GetTable().SetDecorateFn(n.decorate)
|
||||
n.GetTable().SetColorerFn(render.Namespace{}.ColorerFunc())
|
||||
n.GetTable().SetEnterFn(n.switchNs)
|
||||
n.SetBindKeysFn(n.bindKeys)
|
||||
n.AddBindKeysFn(n.bindKeys)
|
||||
|
||||
return &n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type OpenFaas struct {
|
|||
// NewOpenFaas returns a new viewer.
|
||||
func NewOpenFaas(gvr client.GVR) ResourceViewer {
|
||||
o := OpenFaas{ResourceViewer: NewBrowser(gvr)}
|
||||
o.SetBindKeysFn(o.bindKeys)
|
||||
o.AddBindKeysFn(o.bindKeys)
|
||||
o.GetTable().SetEnterFn(o.showPods)
|
||||
o.GetTable().SetColorerFn(render.OpenFaas{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ func NewPortForward(gvr client.GVR) ResourceViewer {
|
|||
p.GetTable().SetColorerFn(render.PortForward{}.ColorerFunc())
|
||||
p.GetTable().SetSortCol(ageCol, true)
|
||||
p.SetContextFn(p.portForwardContext)
|
||||
p.SetBindKeysFn(p.bindKeys)
|
||||
p.AddBindKeysFn(p.bindKeys)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
|
@ -117,7 +118,7 @@ func extractPort(p string) string {
|
|||
func extractContainer(p string) string {
|
||||
tokens := strings.Split(p, ":")
|
||||
if len(tokens) != 2 {
|
||||
return "n/a"
|
||||
return render.NAValue
|
||||
}
|
||||
|
||||
co, _ := client.Namespaced(tokens[0])
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@ type PortForwardExtender struct {
|
|||
|
||||
// NewPortForwardExtender returns a new extender.
|
||||
func NewPortForwardExtender(r ResourceViewer) ResourceViewer {
|
||||
s := PortForwardExtender{ResourceViewer: r}
|
||||
s.bindKeys(s.Actions())
|
||||
p := PortForwardExtender{ResourceViewer: r}
|
||||
p.AddBindKeysFn(p.bindKeys)
|
||||
|
||||
return &s
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *PortForwardExtender) bindKeys(aa ui.KeyActions) {
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ type Pod struct {
|
|||
func NewPod(gvr client.GVR) ResourceViewer {
|
||||
p := Pod{}
|
||||
p.ResourceViewer = NewPortForwardExtender(
|
||||
NewSetImageExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), p.selectedContainer),
|
||||
),
|
||||
)
|
||||
p.SetBindKeysFn(p.bindKeys)
|
||||
p.AddBindKeysFn(p.bindKeys)
|
||||
p.GetTable().SetEnterFn(p.showContainers)
|
||||
p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())
|
||||
p.GetTable().SetDecorateFn(p.portForwardIndicator)
|
||||
|
|
@ -63,7 +63,7 @@ func (p *Pod) bindDangerousKeys(aa ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (p *Pod) bindKeys(aa ui.KeyActions) {
|
||||
if !p.App().Config.K9s.GetReadOnly() {
|
||||
if !p.App().Config.K9s.IsReadOnly() {
|
||||
p.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
@ -351,7 +351,6 @@ func podIsRunning(f dao.Factory, path string) bool {
|
|||
}
|
||||
|
||||
var re render.Pod
|
||||
log.Debug().Msgf("Phase %#v", re.Phase(po))
|
||||
return re.Phase(po) == render.Running
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func NewPolicy(app *App, subject, name string) *Policy {
|
|||
subjectName: name,
|
||||
}
|
||||
p.GetTable().SetColorerFn(render.Policy{}.ColorerFunc())
|
||||
p.SetBindKeysFn(p.bindKeys)
|
||||
p.AddBindKeysFn(p.bindKeys)
|
||||
p.GetTable().SetSortCol(nameCol, false)
|
||||
p.SetContextFn(p.subjectCtx)
|
||||
p.GetTable().SetEnterFn(blankEnterFn)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ func NewPopeye(gvr client.GVR) ResourceViewer {
|
|||
p.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||
p.GetTable().SetSortCol("SCORE%", true)
|
||||
p.GetTable().SetDecorateFn(p.decorateRows)
|
||||
p.SetBindKeysFn(p.bindKeys)
|
||||
p.AddBindKeysFn(p.bindKeys)
|
||||
|
||||
return &p
|
||||
}
|
||||
|
|
|
|||
|
|
@ -264,8 +264,8 @@ func (p *Pulse) SetInstance(string) {}
|
|||
// SetEnvFn sets the custom environment function.
|
||||
func (p *Pulse) SetEnvFn(EnvFunc) {}
|
||||
|
||||
// SetBindKeysFn sets up extra key bindings.
|
||||
func (p *Pulse) SetBindKeysFn(BindKeysFunc) {}
|
||||
// AddBindKeysFn sets up extra key bindings.
|
||||
func (p *Pulse) AddBindKeysFn(BindKeysFunc) {}
|
||||
|
||||
// SetContextFn sets custom context.
|
||||
func (p *Pulse) SetContextFn(ContextFunc) {}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ func NewPersistentVolumeClaim(gvr client.GVR) ResourceViewer {
|
|||
v := PersistentVolumeClaim{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
v.SetBindKeysFn(v.bindKeys)
|
||||
v.AddBindKeysFn(v.bindKeys)
|
||||
v.GetTable().SetColorerFn(render.PersistentVolumeClaim{}.ColorerFunc())
|
||||
|
||||
return &v
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func NewRbac(gvr client.GVR) ResourceViewer {
|
|||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
r.GetTable().SetColorerFn(render.Rbac{}.ColorerFunc())
|
||||
r.SetBindKeysFn(r.bindKeys)
|
||||
r.AddBindKeysFn(r.bindKeys)
|
||||
r.GetTable().SetSortCol("APIGROUP", true)
|
||||
r.GetTable().SetEnterFn(blankEnterFn)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func NewReference(gvr client.GVR) ResourceViewer {
|
|||
r.GetTable().SetColorerFn(render.Reference{}.ColorerFunc())
|
||||
r.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||
r.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||
r.SetBindKeysFn(r.bindKeys)
|
||||
r.AddBindKeysFn(r.bindKeys)
|
||||
|
||||
return &r
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,16 @@ type RestartExtender struct {
|
|||
// NewRestartExtender returns a new extender.
|
||||
func NewRestartExtender(v ResourceViewer) ResourceViewer {
|
||||
r := RestartExtender{ResourceViewer: v}
|
||||
r.bindKeys(v.Actions())
|
||||
v.AddBindKeysFn(r.bindKeys)
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
// BindKeys creates additional menu actions.
|
||||
func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
||||
if r.App().Config.K9s.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
aa.Add(ui.KeyActions{
|
||||
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func NewReplicaSet(gvr client.GVR) ResourceViewer {
|
|||
r := ReplicaSet{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
r.SetBindKeysFn(r.bindKeys)
|
||||
r.AddBindKeysFn(r.bindKeys)
|
||||
r.GetTable().SetEnterFn(r.showPods)
|
||||
r.GetTable().SetColorerFn(render.ReplicaSet{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ func NewServiceAccount(gvr client.GVR) ResourceViewer {
|
|||
s := ServiceAccount{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
s.SetBindKeysFn(s.bindKeys)
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
|
||||
return &s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,7 +315,10 @@ func (s *Sanitizer) SetEnvFn(EnvFunc) {}
|
|||
func (s *Sanitizer) Refresh() {}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (s *Sanitizer) BufferChanged(q string) {
|
||||
func (s *Sanitizer) BufferChanged(q string) {}
|
||||
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (s *Sanitizer) BufferCompleted(q string) {
|
||||
s.update(s.filter(s.model.Peek()))
|
||||
}
|
||||
|
||||
|
|
@ -360,8 +363,8 @@ func (s *Sanitizer) Stop() {
|
|||
s.CmdBuff().RemoveListener(s)
|
||||
}
|
||||
|
||||
// SetBindKeysFn sets up extra key bindings.
|
||||
func (s *Sanitizer) SetBindKeysFn(BindKeysFunc) {}
|
||||
// AddBindKeysFn sets up extra key bindings.
|
||||
func (s *Sanitizer) AddBindKeysFn(BindKeysFunc) {}
|
||||
|
||||
// SetContextFn sets custom context.
|
||||
func (s *Sanitizer) SetContextFn(f ContextFunc) {
|
||||
|
|
|
|||
|
|
@ -21,12 +21,15 @@ type ScaleExtender struct {
|
|||
// NewScaleExtender returns a new extender.
|
||||
func NewScaleExtender(r ResourceViewer) ResourceViewer {
|
||||
s := ScaleExtender{ResourceViewer: r}
|
||||
s.bindKeys(s.Actions())
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *ScaleExtender) bindKeys(aa ui.KeyActions) {
|
||||
if s.App().Config.K9s.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
aa.Add(ui.KeyActions{
|
||||
ui.KeyS: ui.NewKeyAction("Scale", s.scaleCmd, true),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func NewSecret(gvr client.GVR) ResourceViewer {
|
|||
s := Secret{
|
||||
ResourceViewer: NewBrowser(gvr),
|
||||
}
|
||||
s.SetBindKeysFn(s.bindKeys)
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
|
||||
return &s
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,14 +21,14 @@ func NewStatefulSet(gvr client.GVR) ResourceViewer {
|
|||
ResourceViewer: NewPortForwardExtender(
|
||||
NewRestartExtender(
|
||||
NewScaleExtender(
|
||||
NewSetImageExtender(
|
||||
NewImageExtender(
|
||||
NewLogsExtender(NewBrowser(gvr), nil),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
}
|
||||
s.SetBindKeysFn(s.bindKeys)
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
s.GetTable().SetEnterFn(s.showPods)
|
||||
s.GetTable().SetColorerFn(render.StatefulSet{}.ColorerFunc())
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func NewService(gvr client.GVR) ResourceViewer {
|
|||
NewLogsExtender(NewBrowser(gvr), nil),
|
||||
),
|
||||
}
|
||||
s.SetBindKeysFn(s.bindKeys)
|
||||
s.AddBindKeysFn(s.bindKeys)
|
||||
s.GetTable().SetEnterFn(s.showPods)
|
||||
|
||||
return &s
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ type Table struct {
|
|||
app *App
|
||||
enterFn EnterFunc
|
||||
envFn EnvFunc
|
||||
bindKeysFn BindKeysFunc
|
||||
bindKeysFn []BindKeysFunc
|
||||
}
|
||||
|
||||
// NewTable returns a new viewer.
|
||||
|
|
@ -73,8 +73,10 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
// Name returns the table name.
|
||||
func (t *Table) Name() string { return t.GVR().R() }
|
||||
|
||||
// SetBindKeysFn adds additional key bindings.
|
||||
func (t *Table) SetBindKeysFn(f BindKeysFunc) { t.bindKeysFn = f }
|
||||
// AddBindKeysFn adds additional key bindings.
|
||||
func (t *Table) AddBindKeysFn(f BindKeysFunc) {
|
||||
t.bindKeysFn = append(t.bindKeysFn, f)
|
||||
}
|
||||
|
||||
// SetEnvFn sets a function to pull viewer env vars for plugins.
|
||||
func (t *Table) SetEnvFn(f EnvFunc) { t.envFn = f }
|
||||
|
|
@ -125,11 +127,14 @@ func (t *Table) SetEnterFn(f EnterFunc) {
|
|||
// SetExtraActionsFn specifies custom keyboard behavior.
|
||||
func (t *Table) SetExtraActionsFn(BoostActionsFunc) {}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (t *Table) BufferChanged(s string) {
|
||||
// BufferCompleted indicates input was accepted.
|
||||
func (t *Table) BufferCompleted(s string) {
|
||||
t.Filter(s)
|
||||
}
|
||||
|
||||
// BufferChanged indicates the buffer was changed.
|
||||
func (t *Table) BufferChanged(s string) {}
|
||||
|
||||
// BufferActive indicates the buff activity changed.
|
||||
func (t *Table) BufferActive(state bool, k model.BufferKind) {
|
||||
t.app.BufferActive(state, k)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: the-map
|
||||
data:
|
||||
altGreeting: "Good Morning!"
|
||||
enableRisky: "false"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
commonLabels:
|
||||
app: fred
|
||||
|
||||
resources:
|
||||
- cm.yaml
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: the-map
|
||||
data:
|
||||
altGreeting: "Good Morning!"
|
||||
enableRisky: "false"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
commonLabels:
|
||||
app: fred
|
||||
|
||||
resources:
|
||||
- cm.yaml
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
commonLabels:
|
||||
app: fred
|
||||
|
||||
resources:
|
||||
- cm.yaml
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: the-map
|
||||
data:
|
||||
altGreeting: "Good Morning!"
|
||||
enableRisky: "false"
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: the-map
|
||||
data:
|
||||
altGreeting: "Good Morning!"
|
||||
enableRisky: "false"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
commonLabels:
|
||||
app: fred
|
||||
|
||||
resources:
|
||||
- cm.yaml
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: the-map
|
||||
data:
|
||||
altGreeting: "Good Morning!"
|
||||
enableRisky: "false"
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
commonLabels:
|
||||
app: fred
|
||||
|
||||
resources:
|
||||
- cm.yaml
|
||||
|
|
@ -86,8 +86,8 @@ type ResourceViewer interface {
|
|||
// SetContextFn provision a custom context.
|
||||
SetContextFn(ContextFunc)
|
||||
|
||||
// SetBindKeys provision additional key bindings.
|
||||
SetBindKeysFn(BindKeysFunc)
|
||||
// AddBindKeys provision additional key bindings.
|
||||
AddBindKeysFn(BindKeysFunc)
|
||||
|
||||
// SetInstance sets a parent FQN
|
||||
SetInstance(string)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ type User struct {
|
|||
func NewUser(gvr client.GVR) ResourceViewer {
|
||||
u := User{ResourceViewer: NewBrowser(gvr)}
|
||||
u.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
||||
u.SetBindKeysFn(u.bindKeys)
|
||||
u.AddBindKeysFn(u.bindKeys)
|
||||
u.SetContextFn(u.subjectCtx)
|
||||
|
||||
return &u
|
||||
|
|
|
|||
|
|
@ -162,12 +162,12 @@ func (x *Xray) refreshActions() {
|
|||
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)
|
||||
aa[ui.KeyP] = 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)
|
||||
aa[ui.KeyP] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
||||
}
|
||||
x.Actions().Add(aa)
|
||||
}
|
||||
|
|
@ -545,11 +545,14 @@ 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) {
|
||||
// BufferCompleted indicates the buffer was changed.
|
||||
func (x *Xray) BufferCompleted(s string) {
|
||||
x.update(x.filter(x.model.Peek()))
|
||||
}
|
||||
|
||||
// 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 model.BufferKind) {
|
||||
x.app.BufferActive(state, k)
|
||||
|
|
@ -589,7 +592,7 @@ func (x *Xray) Stop() {
|
|||
}
|
||||
|
||||
// SetBindKeysFn sets up extra key bindings.
|
||||
func (x *Xray) SetBindKeysFn(BindKeysFunc) {}
|
||||
func (x *Xray) AddBindKeysFn(BindKeysFunc) {}
|
||||
|
||||
// SetContextFn sets custom context.
|
||||
func (x *Xray) SetContextFn(ContextFunc) {}
|
||||
|
|
|
|||
Loading…
Reference in New Issue