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 _ config.KubeSettings = (*client.Config)(nil)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
version, commit, date = "dev", "dev", "n/a"
|
version, commit, date = "dev", "dev", client.NA
|
||||||
k9sFlags *config.Flags
|
k9sFlags *config.Flags
|
||||||
k8sFlags *genericclioptions.ConfigFlags
|
k8sFlags *genericclioptions.ConfigFlags
|
||||||
demoMode = new(bool)
|
demoMode = new(bool)
|
||||||
|
|
@ -215,7 +215,7 @@ func initK9sFlags() {
|
||||||
k9sFlags.ReadOnly,
|
k9sFlags.ReadOnly,
|
||||||
"readonly",
|
"readonly",
|
||||||
false,
|
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...
|
// Helpers...
|
||||||
|
|
||||||
const megaByte = 1024 * 1024
|
// MegaByte represents a megabyte.
|
||||||
|
const MegaByte = 1024 * 1024
|
||||||
|
|
||||||
// ToMB converts bytes to megabytes.
|
// ToMB converts bytes to megabytes.
|
||||||
func ToMB(v int64) int64 {
|
func ToMB(v int64) int64 {
|
||||||
return v / megaByte
|
return v / MegaByte
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToPercentage computes percentage.
|
// ToPercentage computes percentage.
|
||||||
|
|
|
||||||
|
|
@ -29,14 +29,13 @@ func TestToPercentage(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestToMB(t *testing.T) {
|
func TestToMB(t *testing.T) {
|
||||||
const mb = 1024 * 1024
|
|
||||||
uu := []struct {
|
uu := []struct {
|
||||||
v int64
|
v int64
|
||||||
e int64
|
e int64
|
||||||
}{
|
}{
|
||||||
{0, 0},
|
{0, 0},
|
||||||
{2 * mb, 2},
|
{2 * client.MegaByte, 2},
|
||||||
{10 * mb, 10},
|
{10 * client.MegaByte, 10},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range uu {
|
for _, u := range uu {
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,8 @@ func Colorize(s string, c Paint) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANSIColorize colors a string.
|
// ANSIColorize colors a string.
|
||||||
func ANSIColorize(s string, c int) string {
|
func ANSIColorize(text string, color int) string {
|
||||||
return "\033[38;5;" + strconv.Itoa(c) + "m" + s + "\033[0m"
|
return "\033[38;5;" + strconv.Itoa(color) + "m" + text + "\033[0m"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Highlight colorize bytes at given indices.
|
// Highlight colorize bytes at given indices.
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,9 @@ func (k *K9s) GetRefreshRate() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReadOnly returns the readonly setting.
|
// GetReadOnly returns the readonly setting.
|
||||||
func (k *K9s) GetReadOnly() bool {
|
func (k *K9s) IsReadOnly() bool {
|
||||||
readOnly := k.ReadOnly
|
readOnly := k.ReadOnly
|
||||||
if k.manualReadOnly != nil && *k.manualReadOnly {
|
if k.manualReadOnly != nil {
|
||||||
readOnly = *k.manualReadOnly
|
readOnly = *k.manualReadOnly
|
||||||
}
|
}
|
||||||
return readOnly
|
return readOnly
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Render returns a log line as string.
|
// 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)
|
bb := make([]byte, 0, 200)
|
||||||
if showTime {
|
if showTime {
|
||||||
t := l.Timestamp
|
t := l.Timestamp
|
||||||
|
|
@ -92,11 +92,11 @@ func (l *LogItem) Render(c int, showTime bool) []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.Pod != "" {
|
if l.Pod != "" {
|
||||||
bb = append(bb, color.ANSIColorize(l.Pod, c)...)
|
bb = append(bb, color.ANSIColorize(l.Pod, paint)...)
|
||||||
bb = append(bb, ':')
|
bb = append(bb, ':')
|
||||||
}
|
}
|
||||||
if !l.SingleContainer && l.Container != "" {
|
if !l.SingleContainer && l.Container != "" {
|
||||||
bb = append(bb, color.ANSIColorize(l.Container, c)...)
|
bb = append(bb, color.ANSIColorize(l.Container, paint)...)
|
||||||
bb = append(bb, ' ')
|
bb = append(bb, ' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,10 +122,20 @@ func colorFor(n string) int {
|
||||||
type LogItems []*LogItem
|
type LogItems []*LogItem
|
||||||
|
|
||||||
// Lines returns a collection of log lines.
|
// 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))
|
ll := make([]string, len(l))
|
||||||
for i, item := range l {
|
for i, item := range l {
|
||||||
ll[i] = string(item.Render(0, false))
|
ll[i] = string(item.Render(0, showTime))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ll
|
return ll
|
||||||
|
|
@ -154,15 +164,15 @@ func (l LogItems) DumpDebug(m string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter filters out log items based on given filter.
|
// 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 == "" {
|
if q == "" {
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
if IsFuzzySelector(q) {
|
if IsFuzzySelector(q) {
|
||||||
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]))
|
mm, ii := l.fuzzyFilter(strings.TrimSpace(q[2:]), showTime)
|
||||||
return mm, ii, nil
|
return mm, ii, nil
|
||||||
}
|
}
|
||||||
matches, indices, err := l.filterLogs(q)
|
matches, indices, err := l.filterLogs(q, showTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Logs filter failed")
|
log.Error().Err(err).Msgf("Logs filter failed")
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
|
|
@ -172,10 +182,10 @@ func (l LogItems) Filter(q string) ([]int, [][]int, error) {
|
||||||
|
|
||||||
var fuzzyRx = regexp.MustCompile(`\A\-f`)
|
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)
|
q = strings.TrimSpace(q)
|
||||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
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 {
|
for _, m := range mm {
|
||||||
matches = append(matches, m.Index)
|
matches = append(matches, m.Index)
|
||||||
indices = append(indices, m.MatchedIndexes)
|
indices = append(indices, m.MatchedIndexes)
|
||||||
|
|
@ -184,14 +194,14 @@ func (l LogItems) fuzzyFilter(q string) ([]int, [][]int) {
|
||||||
return matches, indices
|
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)
|
rx, err := regexp.Compile(`(?i)` + q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
matches, indices := make([]int, 0, len(l)), make([][]int, 0, 10)
|
||||||
for i, line := range l.Lines() {
|
for i, line := range l.Lines(showTime) {
|
||||||
if locs := rx.FindStringIndex(line); locs != nil {
|
if locs := rx.FindIndex(line); locs != nil {
|
||||||
matches = append(matches, i)
|
matches = append(matches, i)
|
||||||
ii := make([]int, 0, 10)
|
ii := make([]int, 0, 10)
|
||||||
for i := 0; i < len(locs); i += 2 {
|
for i := 0; i < len(locs); i += 2 {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ func TestLogItemsFilter(t *testing.T) {
|
||||||
for _, i := range ii {
|
for _, i := range ii {
|
||||||
i.Pod, i.Container = n, u.opts.Container
|
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)
|
assert.Equal(t, u.err, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
assert.Equal(t, u.e, res)
|
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
|
var tailed bool
|
||||||
for _, co := range po.Spec.InitContainers {
|
for _, co := range po.Spec.InitContainers {
|
||||||
log.Debug().Msgf("Tailing INIT-CO %q", co.Name)
|
|
||||||
opts.Container = co.Name
|
opts.Container = co.Name
|
||||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -213,7 +212,6 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
||||||
tailed = true
|
tailed = true
|
||||||
}
|
}
|
||||||
for _, co := range po.Spec.Containers {
|
for _, co := range po.Spec.Containers {
|
||||||
log.Debug().Msgf("Tailing CO %q", co.Name)
|
|
||||||
opts.Container = co.Name
|
opts.Container = co.Name
|
||||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -221,7 +219,6 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
||||||
tailed = true
|
tailed = true
|
||||||
}
|
}
|
||||||
for _, co := range po.Spec.EphemeralContainers {
|
for _, co := range po.Spec.EphemeralContainers {
|
||||||
log.Debug().Msgf("Tailing EPH-CO %q", co.Name)
|
|
||||||
opts.Container = co.Name
|
opts.Container = co.Name
|
||||||
if err := tailLogs(ctx, p, c, opts); err != nil {
|
if err := tailLogs(ctx, p, c, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -236,7 +233,7 @@ func (p *Pod) TailLogs(ctx context.Context, c LogChan, opts LogOptions) error {
|
||||||
return nil
|
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) {
|
func (p *Pod) ScanSA(ctx context.Context, fqn string, wait bool) (Refs, error) {
|
||||||
ns, n := client.Namespaced(fqn)
|
ns, n := client.Namespaced(fqn)
|
||||||
oo, err := p.Factory.List(p.GVR(), ns, wait, labels.Everything())
|
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:
|
done:
|
||||||
for r := 0; r < logRetryCount; r++ {
|
for r := 0; r < logRetryCount; r++ {
|
||||||
log.Debug().Msgf("Retry logs %d", r)
|
|
||||||
req, err = logger.Logs(opts.Path, opts.ToPodLogOptions())
|
req, err = logger.Logs(opts.Path, opts.ToPodLogOptions())
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// This call will block if nothing is in the stream!!
|
// This call will block if nothing is in the stream!!
|
||||||
if stream, err = req.Stream(ctx); err == nil {
|
if stream, err = req.Stream(ctx); err == nil {
|
||||||
log.Debug().Msgf("Reading logs")
|
|
||||||
go readLogs(stream, c, opts)
|
go readLogs(stream, c, opts)
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -426,18 +421,18 @@ func extractFQN(o runtime.Object) string {
|
||||||
u, ok := o.(*unstructured.Unstructured)
|
u, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o))
|
log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o))
|
||||||
return "na"
|
return client.NA
|
||||||
}
|
}
|
||||||
m, ok := u.Object["metadata"].(map[string]interface{})
|
m, ok := u.Object["metadata"].(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"]))
|
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)
|
n, ok := m["name"].(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"]))
|
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)
|
ns, ok := m["namespace"].(string)
|
||||||
|
|
|
||||||
|
|
@ -174,7 +174,7 @@ func parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Policies {
|
||||||
if nres[0] != '/' {
|
if nres[0] != '/' {
|
||||||
nres = "/" + nres
|
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 {
|
func (c *Cluster) Version() string {
|
||||||
info, err := c.factory.Client().ServerVersion()
|
info, err := c.factory.Client().ServerVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NA
|
return client.NA
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.GitVersion
|
return info.GitVersion
|
||||||
|
|
@ -55,7 +55,7 @@ func (c *Cluster) Version() string {
|
||||||
func (c *Cluster) ContextName() string {
|
func (c *Cluster) ContextName() string {
|
||||||
n, err := c.factory.Client().Config().CurrentContextName()
|
n, err := c.factory.Client().Config().CurrentContextName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NA
|
return client.NA
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +64,7 @@ func (c *Cluster) ContextName() string {
|
||||||
func (c *Cluster) ClusterName() string {
|
func (c *Cluster) ClusterName() string {
|
||||||
n, err := c.factory.Client().Config().CurrentClusterName()
|
n, err := c.factory.Client().Config().CurrentClusterName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NA
|
return client.NA
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +73,7 @@ func (c *Cluster) ClusterName() string {
|
||||||
func (c *Cluster) UserName() string {
|
func (c *Cluster) UserName() string {
|
||||||
n, err := c.factory.Client().Config().CurrentUserName()
|
n, err := c.factory.Client().Config().CurrentUserName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NA
|
return client.NA
|
||||||
}
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,6 @@ type ClusterInfoListener interface {
|
||||||
ClusterInfoUpdated(ClusterMeta)
|
ClusterInfoUpdated(ClusterMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NA indicates data is missing at this time.
|
|
||||||
const NA = "n/a"
|
|
||||||
|
|
||||||
// ClusterMeta represents cluster meta data.
|
// ClusterMeta represents cluster meta data.
|
||||||
type ClusterMeta struct {
|
type ClusterMeta struct {
|
||||||
Context, Cluster string
|
Context, Cluster string
|
||||||
|
|
@ -30,11 +27,11 @@ type ClusterMeta struct {
|
||||||
// NewClusterMeta returns a new instance.
|
// NewClusterMeta returns a new instance.
|
||||||
func NewClusterMeta() ClusterMeta {
|
func NewClusterMeta() ClusterMeta {
|
||||||
return ClusterMeta{
|
return ClusterMeta{
|
||||||
Context: NA,
|
Context: client.NA,
|
||||||
Cluster: NA,
|
Cluster: client.NA,
|
||||||
User: NA,
|
User: client.NA,
|
||||||
K9sVer: NA,
|
K9sVer: client.NA,
|
||||||
K8sVer: NA,
|
K8sVer: client.NA,
|
||||||
Cpu: 0,
|
Cpu: 0,
|
||||||
Mem: 0,
|
Mem: 0,
|
||||||
Ephemeral: 0,
|
Ephemeral: 0,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,37 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
const maxBuff = 10
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
maxBuff = 10
|
||||||
|
|
||||||
|
keyEntryDelay = 300 * time.Millisecond
|
||||||
|
|
||||||
// CommandBuffer represents a command buffer.
|
// CommandBuffer represents a command buffer.
|
||||||
CommandBuffer BufferKind = 1 << iota
|
CommandBuffer BufferKind = 1 << iota
|
||||||
// FilterBuffer represents a filter buffer.
|
// FilterBuffer represents a filter buffer.
|
||||||
FilterBuffer
|
FilterBuffer
|
||||||
)
|
)
|
||||||
|
|
||||||
// BufferKind indicates a buffer type
|
type (
|
||||||
type BufferKind int8
|
// BufferKind indicates a buffer type
|
||||||
|
BufferKind int8
|
||||||
|
|
||||||
// BuffWatcher represents a command buffer listener.
|
// BuffWatcher represents a command buffer listener.
|
||||||
type BuffWatcher interface {
|
BuffWatcher interface {
|
||||||
// Changed indicates the buffer was changed.
|
// BufferCompleted indicates input was accepted.
|
||||||
BufferChanged(s string)
|
BufferCompleted(s string)
|
||||||
|
|
||||||
// Active indicates the buff activity changed.
|
// BufferChanged indicates the buffer was changed.
|
||||||
BufferActive(state bool, kind BufferKind)
|
BufferChanged(s string)
|
||||||
}
|
|
||||||
|
// BufferActive indicates the buff activity changed.
|
||||||
|
BufferActive(state bool, kind BufferKind)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// CmdBuff represents user command input.
|
// CmdBuff represents user command input.
|
||||||
type CmdBuff struct {
|
type CmdBuff struct {
|
||||||
|
|
@ -28,6 +40,7 @@ type CmdBuff struct {
|
||||||
hotKey rune
|
hotKey rune
|
||||||
kind BufferKind
|
kind BufferKind
|
||||||
active bool
|
active bool
|
||||||
|
cancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCmdBuff returns a new command buffer.
|
// NewCmdBuff returns a new command buffer.
|
||||||
|
|
@ -64,13 +77,24 @@ func (c *CmdBuff) GetText() string {
|
||||||
// SetText initializes the buffer with a command.
|
// SetText initializes the buffer with a command.
|
||||||
func (c *CmdBuff) SetText(cmd string) {
|
func (c *CmdBuff) SetText(cmd string) {
|
||||||
c.buff = []rune(cmd)
|
c.buff = []rune(cmd)
|
||||||
c.fireBufferChanged()
|
c.fireBufferCompleted()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a new character to the buffer.
|
// Add adds a new character to the buffer.
|
||||||
func (c *CmdBuff) Add(r rune) {
|
func (c *CmdBuff) Add(r rune) {
|
||||||
c.buff = append(c.buff, r)
|
c.buff = append(c.buff, r)
|
||||||
c.fireBufferChanged()
|
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.
|
// 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.buff = c.buff[:len(c.buff)-1]
|
||||||
c.fireBufferChanged()
|
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.
|
// ClearText clears out command buffer.
|
||||||
func (c *CmdBuff) ClearText(fire bool) {
|
func (c *CmdBuff) ClearText(fire bool) {
|
||||||
c.buff = make([]rune, 0, maxBuff)
|
c.buff = make([]rune, 0, maxBuff)
|
||||||
if fire {
|
if fire {
|
||||||
c.fireBufferChanged()
|
c.fireBufferCompleted()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,6 +130,7 @@ func (c *CmdBuff) ClearText(fire bool) {
|
||||||
func (c *CmdBuff) Reset() {
|
func (c *CmdBuff) Reset() {
|
||||||
c.ClearText(true)
|
c.ClearText(true)
|
||||||
c.SetActive(false)
|
c.SetActive(false)
|
||||||
|
c.fireBufferCompleted()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if no cmd, false otherwise.
|
// 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:]...)
|
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() {
|
func (c *CmdBuff) fireBufferChanged() {
|
||||||
for _, l := range c.listeners {
|
for _, l := range c.listeners {
|
||||||
l.BufferChanged(c.GetText())
|
l.BufferChanged(c.GetText())
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ func (l *testListener) BufferChanged(s string) {
|
||||||
l.text = s
|
l.text = s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *testListener) BufferCompleted(s string) {
|
||||||
|
l.text = s
|
||||||
|
}
|
||||||
|
|
||||||
func (l *testListener) BufferActive(s bool, _ model.BufferKind) {
|
func (l *testListener) BufferActive(s bool, _ model.BufferKind) {
|
||||||
if s {
|
if s {
|
||||||
l.act++
|
l.act++
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,9 @@ func (m *mockSuggestionListener) BufferChanged(s string) {
|
||||||
m.buff++
|
m.buff++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockSuggestionListener) BufferCompleted(s string) {
|
||||||
|
}
|
||||||
|
|
||||||
func (m *mockSuggestionListener) BufferActive(state bool, kind model.BufferKind) {
|
func (m *mockSuggestionListener) BufferActive(state bool, kind model.BufferKind) {
|
||||||
m.active = state
|
m.active = state
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import (
|
||||||
// LogsListener represents a log model listener.
|
// LogsListener represents a log model listener.
|
||||||
type LogsListener interface {
|
type LogsListener interface {
|
||||||
// LogChanged notifies the model changed.
|
// LogChanged notifies the model changed.
|
||||||
LogChanged(dao.LogItems)
|
LogChanged([][]byte)
|
||||||
|
|
||||||
// LogCleanred indicates logs are cleared.
|
// LogCleanred indicates logs are cleared.
|
||||||
LogCleared()
|
LogCleared()
|
||||||
|
|
@ -80,7 +80,7 @@ func (l *Log) SetLogOptions(opts dao.LogOptions) {
|
||||||
|
|
||||||
// Configure sets logger configuration.
|
// Configure sets logger configuration.
|
||||||
func (l *Log) Configure(opts *config.Logger) {
|
func (l *Log) Configure(opts *config.Logger) {
|
||||||
l.logOptions.Lines = int64(opts.BufferSize)
|
l.logOptions.Lines = int64(opts.TailCount)
|
||||||
l.logOptions.SinceSeconds = opts.SinceSeconds
|
l.logOptions.SinceSeconds = opts.SinceSeconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +113,9 @@ func (l *Log) Clear() {
|
||||||
// Refresh refreshes the logs.
|
// Refresh refreshes the logs.
|
||||||
func (l *Log) Refresh() {
|
func (l *Log) Refresh() {
|
||||||
l.fireLogCleared()
|
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.
|
// Restart restarts the logger.
|
||||||
|
|
@ -149,7 +151,9 @@ func (l *Log) Set(items dao.LogItems) {
|
||||||
l.mx.Unlock()
|
l.mx.Unlock()
|
||||||
|
|
||||||
l.fireLogCleared()
|
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.
|
// ClearFilter resets the log filter if any.
|
||||||
|
|
@ -161,7 +165,9 @@ func (l *Log) ClearFilter() {
|
||||||
l.mx.Unlock()
|
l.mx.Unlock()
|
||||||
|
|
||||||
l.fireLogCleared()
|
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.
|
// Filter filters the model using either fuzzy or regexp.
|
||||||
|
|
@ -169,6 +175,13 @@ func (l *Log) Filter(q string) {
|
||||||
l.mx.Lock()
|
l.mx.Lock()
|
||||||
defer l.mx.Unlock()
|
defer l.mx.Unlock()
|
||||||
|
|
||||||
|
if len(q) == 0 {
|
||||||
|
l.filter, l.filtering = "", false
|
||||||
|
l.fireLogCleared()
|
||||||
|
l.fireLogBuffChanged(l.lines)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
l.filter = q
|
l.filter = q
|
||||||
if l.filtering {
|
if l.filtering {
|
||||||
return
|
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 == "" {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// No filter!
|
// No filter!
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
return lines, nil
|
ll := make([][]byte, len(l.lines))
|
||||||
|
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||||
|
return ll, nil
|
||||||
}
|
}
|
||||||
// Blank filter
|
// Blank filter
|
||||||
if len(matches) == 0 {
|
if len(matches) == 0 {
|
||||||
return nil, nil
|
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 {
|
for i, idx := range matches {
|
||||||
item := lines[idx].Clone()
|
filtered = append(filtered, color.Highlight(lines[idx], indices[i], 209))
|
||||||
item.Bytes = color.Highlight(item.Bytes, indices[i], 209)
|
|
||||||
filtered = append(filtered, item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filtered, nil
|
return filtered, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
|
func (l *Log) fireLogBuffChanged(lines dao.LogItems) {
|
||||||
filtered := lines
|
ll := make([][]byte, len(l.lines))
|
||||||
if l.filter != "" {
|
if l.filter == "" {
|
||||||
var err error
|
l.lines.Render(l.logOptions.ShowTimestamp, ll)
|
||||||
filtered, err = applyFilter(l.filter, lines)
|
} else {
|
||||||
|
ff, err := l.applyFilter(l.filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.fireLogError(err)
|
l.fireLogError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
ll = ff
|
||||||
}
|
}
|
||||||
if len(filtered) > 0 {
|
if len(ll) > 0 {
|
||||||
l.fireLogChanged(filtered)
|
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 {
|
for _, lis := range l.listeners {
|
||||||
lis.LogChanged(lines)
|
lis.LogChanged(lines)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,8 @@ func newMockLogView() *mockLogView {
|
||||||
return &mockLogView{}
|
return &mockLogView{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *mockLogView) LogChanged(d dao.LogItems) {
|
func (t *mockLogView) LogChanged(ll [][]byte) {
|
||||||
t.count += len(d.Lines())
|
t.count += len(ll)
|
||||||
}
|
}
|
||||||
func (t *mockLogView) LogCleared() {}
|
func (t *mockLogView) LogCleared() {}
|
||||||
func (t *mockLogView) LogFailed(err error) {}
|
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.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
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) {
|
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.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
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) {
|
func TestLogAppend(t *testing.T) {
|
||||||
|
|
@ -153,9 +153,11 @@ func TestLogAppend(t *testing.T) {
|
||||||
|
|
||||||
v := newTestView()
|
v := newTestView()
|
||||||
m.AddListener(v)
|
m.AddListener(v)
|
||||||
items := dao.LogItems{dao.NewLogItemFromString("blah blah")}
|
items := dao.LogItems{
|
||||||
|
dao.NewLogItemFromString("blah blah"),
|
||||||
|
}
|
||||||
m.Set(items)
|
m.Set(items)
|
||||||
assert.Equal(t, items, v.data)
|
assert.Equal(t, items.Lines(false), v.data)
|
||||||
|
|
||||||
data := dao.LogItems{
|
data := dao.LogItems{
|
||||||
dao.NewLogItemFromString("line1"),
|
dao.NewLogItemFromString("line1"),
|
||||||
|
|
@ -165,13 +167,13 @@ func TestLogAppend(t *testing.T) {
|
||||||
m.Append(d)
|
m.Append(d)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 1, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, items, v.data)
|
assert.Equal(t, items.Lines(false), v.data)
|
||||||
|
|
||||||
m.Notify()
|
m.Notify()
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 2, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
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) {
|
func TestLogTimedout(t *testing.T) {
|
||||||
|
|
@ -196,7 +198,7 @@ func TestLogTimedout(t *testing.T) {
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
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"
|
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 {
|
type testView struct {
|
||||||
data dao.LogItems
|
data [][]byte
|
||||||
dataCalled int
|
dataCalled int
|
||||||
clearCalled int
|
clearCalled int
|
||||||
errCalled int
|
errCalled int
|
||||||
|
|
@ -223,13 +225,13 @@ func newTestView() *testView {
|
||||||
return &testView{}
|
return &testView{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testView) LogChanged(d dao.LogItems) {
|
func (t *testView) LogChanged(ll [][]byte) {
|
||||||
t.data = d
|
t.data = ll
|
||||||
t.dataCalled++
|
t.dataCalled++
|
||||||
}
|
}
|
||||||
func (t *testView) LogCleared() {
|
func (t *testView) LogCleared() {
|
||||||
t.clearCalled++
|
t.clearCalled++
|
||||||
t.data = dao.LogItems{}
|
t.data = nil
|
||||||
}
|
}
|
||||||
func (t *testView) LogFailed(err error) {
|
func (t *testView) LogFailed(err error) {
|
||||||
fmt.Println("LogErr", err)
|
fmt.Println("LogErr", err)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func TestTableReconcile(t *testing.T) {
|
||||||
err := ta.reconcile(ctx)
|
err := ta.reconcile(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
data := ta.Peek()
|
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, 1, len(data.RowEvents))
|
||||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
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.Nil(t, hydrate("blee", oo, rr, render.Pod{}))
|
||||||
assert.Equal(t, 1, len(rr))
|
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) {
|
func TestTableGenericHydrate(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ func TestTableRefresh(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
ctx = context.WithValue(ctx, internal.KeyWithMetrics, false)
|
||||||
ta.Refresh(ctx)
|
ta.Refresh(ctx)
|
||||||
data := ta.Peek()
|
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, 1, len(data.RowEvents))
|
||||||
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
assert.Equal(t, client.NamespaceAll, data.Namespace)
|
||||||
assert.Equal(t, 1, l.count)
|
assert.Equal(t, 1, l.count)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,8 @@ func (Container) Header(ns string) Header {
|
||||||
HeaderColumn{Name: "INIT"},
|
HeaderColumn{Name: "INIT"},
|
||||||
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||||
HeaderColumn{Name: "PROBES(L:R)"},
|
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: "CPU", Align: tview.AlignRight, MX: true},
|
||||||
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
||||||
HeaderColumn{Name: "%CPU/R", 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)
|
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"
|
ready, state, restarts := "false", MissingValue, "0"
|
||||||
if co.Status != nil {
|
if co.Status != nil {
|
||||||
ready, state, restarts = boolToStr(co.Status.Ready), ToContainerState(co.Status.State), strconv.Itoa(int(co.Status.RestartCount))
|
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),
|
boolToStr(co.IsInit),
|
||||||
restarts,
|
restarts,
|
||||||
probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe),
|
probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe),
|
||||||
|
ToResourcesMc(res),
|
||||||
|
ToResourcesMi(res),
|
||||||
cur.cpu,
|
cur.cpu,
|
||||||
cur.mem,
|
cur.mem,
|
||||||
perc.cpu,
|
perc.cpu,
|
||||||
|
|
@ -140,8 +144,24 @@ func (Container) diagnose(state, ready string) error {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// 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()
|
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 {
|
if mx == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -149,11 +169,10 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p, l met
|
||||||
cpu := mx.Usage.Cpu().MilliValue()
|
cpu := mx.Usage.Cpu().MilliValue()
|
||||||
mem := client.ToMB(mx.Usage.Memory().Value())
|
mem := client.ToMB(mx.Usage.Memory().Value())
|
||||||
c = metric{
|
c = metric{
|
||||||
cpu: ToMillicore(cpu),
|
cpu: ToMc(cpu),
|
||||||
mem: ToMi(mem),
|
mem: ToMi(mem),
|
||||||
}
|
}
|
||||||
|
|
||||||
rcpu, rmem := containerResources(*co)
|
|
||||||
if rcpu != nil {
|
if rcpu != nil {
|
||||||
p.cpu = client.ToPercentageStr(cpu, rcpu.MilliValue())
|
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()))
|
p.mem = client.ToPercentageStr(mem, client.ToMB(rmem.Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
lcpu, lmem := containerLimits(*co)
|
|
||||||
if lcpu != nil {
|
if lcpu != nil {
|
||||||
l.cpu = client.ToPercentageStr(cpu, lcpu.MilliValue())
|
l.cpu = client.ToPercentageStr(cpu, lcpu.MilliValue())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,10 @@ func TestContainer(t *testing.T) {
|
||||||
"false",
|
"false",
|
||||||
"0",
|
"0",
|
||||||
"off:off",
|
"off:off",
|
||||||
"10",
|
"20m/20m",
|
||||||
"20",
|
"100Mi/100Mi",
|
||||||
|
"10m",
|
||||||
|
"20Mi",
|
||||||
"50",
|
"50",
|
||||||
"20",
|
"20",
|
||||||
"50",
|
"50",
|
||||||
|
|
|
||||||
|
|
@ -57,13 +57,13 @@ func extractMetaField(m map[string]interface{}, field string) string {
|
||||||
f, ok := m[field]
|
f, ok := m[field]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Err(fmt.Errorf("failed to extract field from meta %s", field))
|
log.Error().Err(fmt.Errorf("failed to extract field from meta %s", field))
|
||||||
return "n/a"
|
return NAValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, ok := f.(string)
|
fs, ok := f.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Err(fmt.Errorf("failed to extract string from field %s", field))
|
log.Error().Err(fmt.Errorf("failed to extract string from field %s", field))
|
||||||
return "n/a"
|
return NAValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return fs
|
return fs
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
runewidth "github.com/mattn/go-runewidth"
|
runewidth "github.com/mattn/go-runewidth"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
|
@ -63,13 +64,6 @@ func Happy(ns string, h Header, r Row) bool {
|
||||||
return strings.TrimSpace(r.Fields[validCol]) == ""
|
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 {
|
func asStatus(err error) string {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -257,14 +251,64 @@ func mapToIfc(m interface{}) (s string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToMillicore shows cpu reading for human.
|
// ToResourcesMi prints out request:limit mem resources.
|
||||||
func ToMillicore(v int64) string {
|
func ToResourcesMi(res resources) string {
|
||||||
return strconv.Itoa(int(v))
|
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 {
|
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 {
|
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 {
|
uu := []struct {
|
||||||
v int64
|
v int64
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
{0, "0"},
|
{0, "0m"},
|
||||||
{2, "2"},
|
{2, "2m"},
|
||||||
{1000, "1000"},
|
{1000, "1,000m"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range uu {
|
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
|
v int64
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
{0, "0"},
|
{0, "0Mi"},
|
||||||
{2, "2"},
|
{2, "2Mi"},
|
||||||
{1000, "1000"},
|
{1000, "1,000Mi"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range uu {
|
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())
|
cpu, mem := mx.Usage.Cpu().MilliValue(), client.ToMB(mx.Usage.Memory().Value())
|
||||||
c = metric{
|
c = metric{
|
||||||
cpu: ToMillicore(cpu),
|
cpu: ToMc(cpu),
|
||||||
mem: ToMi(mem),
|
mem: ToMi(mem),
|
||||||
}
|
}
|
||||||
|
|
||||||
acpu, amem := no.Status.Allocatable.Cpu().MilliValue(), client.ToMB(no.Status.Allocatable.Memory().Value())
|
acpu, amem := no.Status.Allocatable.Cpu().MilliValue(), client.ToMB(no.Status.Allocatable.Memory().Value())
|
||||||
a = metric{
|
a = metric{
|
||||||
cpu: ToMillicore(acpu),
|
cpu: ToMc(acpu),
|
||||||
mem: ToMi(amem),
|
mem: ToMi(amem),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func TestNodeRender(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "minikube", r.ID)
|
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])
|
assert.Equal(t, e, r.Fields[:14])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ func TestPodDisruptionBudgetRender(t *testing.T) {
|
||||||
c.Render(load(t, "pdb"), "", &r)
|
c.Render(load(t, "pdb"), "", &r)
|
||||||
|
|
||||||
assert.Equal(t, "default/fred", r.ID)
|
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: "READY"},
|
||||||
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight},
|
||||||
HeaderColumn{Name: "STATUS"},
|
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: "CPU", Align: tview.AlignRight, MX: true},
|
||||||
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
HeaderColumn{Name: "MEM", Align: tview.AlignRight, MX: true},
|
||||||
HeaderColumn{Name: "%CPU/R", 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
|
ss := po.Status.ContainerStatuses
|
||||||
cr, _, rc := p.Statuses(ss)
|
cr, _, rc := p.Statuses(ss)
|
||||||
c, perc := p.gatherPodMX(&po, pwm.MX)
|
c, perc, res := p.gatherPodMX(&po, pwm.MX)
|
||||||
phase := p.Phase(&po)
|
phase := p.Phase(&po)
|
||||||
r.ID = client.MetaFQN(po.ObjectMeta)
|
r.ID = client.MetaFQN(po.ObjectMeta)
|
||||||
r.Fields = Fields{
|
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(cr) + "/" + strconv.Itoa(len(ss)),
|
||||||
strconv.Itoa(rc),
|
strconv.Itoa(rc),
|
||||||
phase,
|
phase,
|
||||||
|
ToResourcesMc(res),
|
||||||
|
ToResourcesMi(res),
|
||||||
c.cpu,
|
c.cpu,
|
||||||
c.mem,
|
c.mem,
|
||||||
perc.cpu,
|
perc.cpu,
|
||||||
|
|
@ -149,7 +153,19 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object {
|
||||||
return p
|
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()
|
c, p = noMetric(), noMetric()
|
||||||
if mx == nil {
|
if mx == nil {
|
||||||
return
|
return
|
||||||
|
|
@ -161,12 +177,15 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) {
|
||||||
}
|
}
|
||||||
cpu, mem := currentRes(mx)
|
cpu, mem := currentRes(mx)
|
||||||
c = metric{
|
c = metric{
|
||||||
cpu: ToMillicore(cpu.MilliValue()),
|
cpu: ToMc(cpu.MilliValue()),
|
||||||
mem: ToMi(client.ToMB(mem.Value())),
|
mem: ToMi(client.ToMB(mem.Value())),
|
||||||
}
|
}
|
||||||
|
|
||||||
rc, rm := resourceRequests(pod.Spec.Containers)
|
rc, rm := podRequests(pod.Spec)
|
||||||
lc, lm := resourceLimits(pod.Spec.Containers)
|
lc, lm := podLimits(pod.Spec)
|
||||||
|
r = make(resources, 4)
|
||||||
|
r[requestCPU], r[requestMEM] = rc, rm
|
||||||
|
r[limitCPU], r[limitMEM] = lc, lm
|
||||||
p = metric{
|
p = metric{
|
||||||
cpu: client.ToPercentageStr(cpu.MilliValue(), rc.MilliValue()),
|
cpu: client.ToPercentageStr(cpu.MilliValue(), rc.MilliValue()),
|
||||||
mem: client.ToPercentageStr(client.ToMB(mem.Value()), client.ToMB(rm.Value())),
|
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()
|
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 {
|
for _, co := range cc {
|
||||||
limit := co.Resources.Limits
|
limit := co.Resources.Limits
|
||||||
if len(limit) == 0 {
|
if len(limit) == 0 {
|
||||||
|
|
@ -215,7 +235,28 @@ func resourceLimits(cc []v1.Container) (cpu, mem resource.Quantity) {
|
||||||
return
|
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 {
|
for _, co := range cc {
|
||||||
c, m := containerResources(co)
|
c, m := containerResources(co)
|
||||||
if c == nil || m == nil {
|
if c == nil || m == nil {
|
||||||
|
|
@ -230,6 +271,7 @@ func resourceRequests(cc []v1.Container) (cpu, mem resource.Quantity) {
|
||||||
mem.Add(*m)
|
mem.Add(*m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -159,8 +159,8 @@ func TestPodRender(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "default/nginx", r.ID)
|
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"}
|
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[:15])
|
assert.Equal(t, e, r.Fields[:17])
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkPodRender(b *testing.B) {
|
func BenchmarkPodRender(b *testing.B) {
|
||||||
|
|
@ -190,8 +190,8 @@ func TestPodInitRender(t *testing.T) {
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "default/nginx", r.ID)
|
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"}
|
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[:15])
|
assert.Equal(t, e, r.Fields[:17])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -40,4 +40,7 @@ const (
|
||||||
|
|
||||||
// UnknownValue represents an unknown.
|
// UnknownValue represents an unknown.
|
||||||
UnknownValue = "<unknown>"
|
UnknownValue = "<unknown>"
|
||||||
|
|
||||||
|
// UnsetValue represent an unset value
|
||||||
|
UnsetValue = ""
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,9 @@ func (a *App) SetRunning(f bool) {
|
||||||
a.running = f
|
a.running = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferCompleted indicates input was accepted.
|
||||||
|
func (a *App) BufferCompleted(s string) {}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// BufferChanged indicates the buffer was changed.
|
||||||
func (a *App) BufferChanged(s string) {}
|
func (a *App) BufferChanged(s string) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func Pad(s string, width int) string {
|
||||||
func toAgeHuman(s string) string {
|
func toAgeHuman(s string) string {
|
||||||
d, err := time.ParseDuration(s)
|
d, err := time.ParseDuration(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "n/a"
|
return render.NAValue
|
||||||
}
|
}
|
||||||
|
|
||||||
return duration.HumanDuration(d)
|
return duration.HumanDuration(d)
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,9 @@ func (p *Prompt) write(text, suggest string) {
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Event Listener protocol...
|
// Event Listener protocol...
|
||||||
|
|
||||||
|
// BufferCompleted indicates input was accepted.
|
||||||
|
func (p *Prompt) BufferCompleted(s string) {}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// BufferChanged indicates the buffer was changed.
|
||||||
func (p *Prompt) BufferChanged(s string) {
|
func (p *Prompt) BufferChanged(s string) {
|
||||||
p.update(s)
|
p.update(s)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ func TestCmdNew(t *testing.T) {
|
||||||
model := model.NewFishBuff(':', model.CommandBuffer)
|
model := model.NewFishBuff(':', model.CommandBuffer)
|
||||||
v.SetModel(model)
|
v.SetModel(model)
|
||||||
model.AddListener(v)
|
model.AddListener(v)
|
||||||
model.SetText("blee")
|
for _, r := range "blee" {
|
||||||
|
model.Add(r)
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, "\x00> [::b]blee\n", v.GetText(false))
|
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().SetColorerFn(render.Alias{}.ColorerFunc())
|
||||||
a.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
a.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
||||||
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
||||||
a.SetBindKeysFn(a.bindKeys)
|
a.AddBindKeysFn(a.bindKeys)
|
||||||
a.SetContextFn(a.aliasContext)
|
a.SetContextFn(a.aliasContext)
|
||||||
|
|
||||||
return &a
|
return &a
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ func TestAliasSearch(t *testing.T) {
|
||||||
v.App().Prompt().SendStrokes("blee")
|
v.App().Prompt().SendStrokes("blee")
|
||||||
|
|
||||||
assert.Equal(t, 3, v.GetTable().GetColumnCount())
|
assert.Equal(t, 3, v.GetTable().GetColumnCount())
|
||||||
|
time.Sleep(1_000 * time.Millisecond)
|
||||||
assert.Equal(t, 2, v.GetTable().GetRowCount())
|
assert.Equal(t, 2, v.GetTable().GetRowCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -62,6 +63,8 @@ type buffL struct {
|
||||||
func (b *buffL) BufferChanged(s string) {
|
func (b *buffL) BufferChanged(s string) {
|
||||||
b.changed++
|
b.changed++
|
||||||
}
|
}
|
||||||
|
func (b *buffL) BufferCompleted(s string) {}
|
||||||
|
|
||||||
func (b *buffL) BufferActive(state bool, kind model.BufferKind) {
|
func (b *buffL) BufferActive(state bool, kind model.BufferKind) {
|
||||||
b.active++
|
b.active++
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,13 +139,11 @@ func (a *App) layout(ctx context.Context, version string) {
|
||||||
|
|
||||||
func (a *App) initSignals() {
|
func (a *App) initSignals() {
|
||||||
sig := make(chan os.Signal, 1)
|
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) {
|
go func(sig chan os.Signal) {
|
||||||
s := <-sig
|
<-sig
|
||||||
if s == syscall.SIGHUP {
|
os.Exit(0)
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}(sig)
|
}(sig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,9 +61,9 @@ func (b *Browser) Init(ctx context.Context) error {
|
||||||
b.app.CmdBuff().Reset()
|
b.app.CmdBuff().Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
b.bindKeys()
|
b.bindKeys(b.Actions())
|
||||||
if b.bindKeysFn != nil {
|
for _, f := range b.bindKeysFn {
|
||||||
b.bindKeysFn(b.Actions())
|
f(b.Actions())
|
||||||
}
|
}
|
||||||
b.accessor, err = dao.AccessorFor(b.app.factory, b.GVR())
|
b.accessor, err = dao.AccessorFor(b.app.factory, b.GVR())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -104,8 +104,8 @@ func (b *Browser) suggestFilter() model.SuggestionFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Browser) bindKeys() {
|
func (b *Browser) bindKeys(aa ui.KeyActions) {
|
||||||
b.Actions().Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", b.resetCmd, false),
|
tcell.KeyEscape: ui.NewSharedKeyAction("Filter Reset", b.resetCmd, false),
|
||||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", b.filterCmd, false),
|
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", b.filterCmd, false),
|
||||||
})
|
})
|
||||||
|
|
@ -132,7 +132,6 @@ func (b *Browser) Start() {
|
||||||
|
|
||||||
// Stop terminates browser updates.
|
// Stop terminates browser updates.
|
||||||
func (b *Browser) Stop() {
|
func (b *Browser) Stop() {
|
||||||
log.Debug().Msgf("BRO-STOP %v", b.GVR())
|
|
||||||
if b.cancelFn != nil {
|
if b.cancelFn != nil {
|
||||||
b.cancelFn()
|
b.cancelFn()
|
||||||
b.cancelFn = nil
|
b.cancelFn = nil
|
||||||
|
|
@ -143,7 +142,10 @@ func (b *Browser) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// 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) {
|
if ui.IsLabelSelector(s) {
|
||||||
b.GetModel().SetLabelFilter(ui.TrimLabelSelector(s))
|
b.GetModel().SetLabelFilter(ui.TrimLabelSelector(s))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -437,7 +439,7 @@ func (b *Browser) refreshActions() {
|
||||||
|
|
||||||
if b.app.ConOK() {
|
if b.app.ConOK() {
|
||||||
b.namespaceActions(aa)
|
b.namespaceActions(aa)
|
||||||
if !b.app.Config.K9s.GetReadOnly() {
|
if !b.app.Config.K9s.IsReadOnly() {
|
||||||
if client.Can(b.meta.Verbs, "edit") {
|
if client.Can(b.meta.Verbs, "edit") {
|
||||||
aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true)
|
aa[ui.KeyE] = ui.NewKeyAction("Edit", b.editCmd, true)
|
||||||
}
|
}
|
||||||
|
|
@ -454,11 +456,10 @@ func (b *Browser) refreshActions() {
|
||||||
|
|
||||||
pluginActions(b, aa)
|
pluginActions(b, aa)
|
||||||
hotKeyActions(b, aa)
|
hotKeyActions(b, aa)
|
||||||
b.Actions().Add(aa)
|
for _, f := range b.bindKeysFn {
|
||||||
|
f(aa)
|
||||||
if b.bindKeysFn != nil {
|
|
||||||
b.bindKeysFn(b.Actions())
|
|
||||||
}
|
}
|
||||||
|
b.Actions().Add(aa)
|
||||||
b.app.Menu().HydrateMenu(b.Hints())
|
b.app.Menu().HydrateMenu(b.Hints())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func NewConfigMap(gvr client.GVR) ResourceViewer {
|
||||||
s := ConfigMap{
|
s := ConfigMap{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
s.SetBindKeysFn(s.bindKeys)
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ func NewContainer(gvr client.GVR) ResourceViewer {
|
||||||
c.GetTable().SetEnterFn(c.viewLogs)
|
c.GetTable().SetEnterFn(c.viewLogs)
|
||||||
c.GetTable().SetColorerFn(render.Container{}.ColorerFunc())
|
c.GetTable().SetColorerFn(render.Container{}.ColorerFunc())
|
||||||
c.GetTable().SetDecorateFn(c.decorateRows)
|
c.GetTable().SetDecorateFn(c.decorateRows)
|
||||||
c.SetBindKeysFn(c.bindKeys)
|
c.AddBindKeysFn(c.bindKeys)
|
||||||
c.GetTable().SetDecorateFn(c.portForwardIndicator)
|
c.GetTable().SetDecorateFn(c.portForwardIndicator)
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
|
|
@ -66,7 +66,7 @@ func (c *Container) bindDangerousKeys(aa ui.KeyActions) {
|
||||||
func (c *Container) bindKeys(aa ui.KeyActions) {
|
func (c *Container) bindKeys(aa ui.KeyActions) {
|
||||||
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
|
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
|
||||||
|
|
||||||
if !c.App().Config.K9s.GetReadOnly() {
|
if !c.App().Config.K9s.IsReadOnly() {
|
||||||
c.bindDangerousKeys(aa)
|
c.bindDangerousKeys(aa)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ func NewContext(gvr client.GVR) ResourceViewer {
|
||||||
}
|
}
|
||||||
c.GetTable().SetEnterFn(c.useCtx)
|
c.GetTable().SetEnterFn(c.useCtx)
|
||||||
c.GetTable().SetColorerFn(render.Context{}.ColorerFunc())
|
c.GetTable().SetColorerFn(render.Context{}.ColorerFunc())
|
||||||
c.SetBindKeysFn(c.bindKeys)
|
c.AddBindKeysFn(c.bindKeys)
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ type CronJob struct {
|
||||||
// NewCronJob returns a new viewer.
|
// NewCronJob returns a new viewer.
|
||||||
func NewCronJob(gvr client.GVR) ResourceViewer {
|
func NewCronJob(gvr client.GVR) ResourceViewer {
|
||||||
c := CronJob{ResourceViewer: NewBrowser(gvr)}
|
c := CronJob{ResourceViewer: NewBrowser(gvr)}
|
||||||
c.SetBindKeysFn(c.bindKeys)
|
c.AddBindKeysFn(c.bindKeys)
|
||||||
c.GetTable().SetEnterFn(c.showJobs)
|
c.GetTable().SetEnterFn(c.showJobs)
|
||||||
c.GetTable().SetColorerFn(render.CronJob{}.ColorerFunc())
|
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.
|
// 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.model.Filter(s)
|
||||||
d.updateTitle()
|
d.updateTitle()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,20 @@ import (
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/k9s/internal/ui/dialog"
|
"github.com/derailed/k9s/internal/ui/dialog"
|
||||||
"github.com/gdamore/tcell"
|
"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.
|
// Dir represents a command directory view.
|
||||||
|
|
@ -31,7 +40,7 @@ func NewDir(path string) ResourceViewer {
|
||||||
}
|
}
|
||||||
d.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
d.GetTable().SetBorderFocusColor(tcell.ColorAliceBlue)
|
||||||
d.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
d.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorAliceBlue, tcell.AttrNone)
|
||||||
d.SetBindKeysFn(d.bindKeys)
|
d.AddBindKeysFn(d.bindKeys)
|
||||||
d.SetContextFn(d.dirContext)
|
d.SetContextFn(d.dirContext)
|
||||||
d.GetTable().SetColorerFn(render.Dir{}.ColorerFunc())
|
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)
|
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) {
|
func (d *Dir) bindKeys(aa ui.KeyActions) {
|
||||||
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
||||||
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
|
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
|
||||||
|
if !d.App().Config.K9s.IsReadOnly() {
|
||||||
|
d.bindDangerousKeys(aa)
|
||||||
|
}
|
||||||
aa.Add(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),
|
|
||||||
ui.KeyY: ui.NewKeyAction("YAML", d.viewCmd, true),
|
ui.KeyY: ui.NewKeyAction("YAML", d.viewCmd, true),
|
||||||
tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true),
|
tcell.KeyEnter: ui.NewKeyAction("Goto", d.gotoCmd, true),
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +115,6 @@ func (d *Dir) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Msgf("Selected %q", sel)
|
|
||||||
if !isManifest(sel) {
|
if !isManifest(sel) {
|
||||||
d.App().Flash().Errf("you must select a manifest")
|
d.App().Flash().Errf("you must select a manifest")
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -136,18 +152,62 @@ func (d *Dir) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
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 {
|
func (d *Dir) applyCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
sel := d.GetTable().GetSelectedItem()
|
sel := d.GetTable().GetSelectedItem()
|
||||||
if sel == "" {
|
if sel == "" {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
opts := []string{"-f"}
|
||||||
|
if containsDir(sel) {
|
||||||
|
opts = append(opts, "-R")
|
||||||
|
}
|
||||||
|
if isKustomized(sel) {
|
||||||
|
opts = []string{"-k"}
|
||||||
|
}
|
||||||
d.Stop()
|
d.Stop()
|
||||||
defer d.Start()
|
defer d.Start()
|
||||||
{
|
{
|
||||||
args := make([]string, 0, 10)
|
args := make([]string, 0, 10)
|
||||||
args = append(args, "apply")
|
args = append(args, "apply")
|
||||||
args = append(args, "-f")
|
args = append(args, opts...)
|
||||||
args = append(args, sel)
|
args = append(args, sel)
|
||||||
res, err := runKu(d.App(), shellOpts{clear: false, args: args})
|
res, err := runKu(d.App(), shellOpts{clear: false, args: args})
|
||||||
if err != nil {
|
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(
|
ResourceViewer: NewPortForwardExtender(
|
||||||
NewRestartExtender(
|
NewRestartExtender(
|
||||||
NewScaleExtender(
|
NewScaleExtender(
|
||||||
NewSetImageExtender(
|
NewImageExtender(
|
||||||
NewLogsExtender(
|
NewLogsExtender(
|
||||||
NewBrowser(gvr),
|
NewBrowser(gvr),
|
||||||
nil,
|
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().SetEnterFn(d.showPods)
|
||||||
d.GetTable().SetColorerFn(render.Deployment{}.ColorerFunc())
|
d.GetTable().SetColorerFn(render.Deployment{}.ColorerFunc())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package view_test
|
package view_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
|
@ -13,5 +14,6 @@ func TestDeploy(t *testing.T) {
|
||||||
|
|
||||||
assert.Nil(t, v.Init(makeCtx()))
|
assert.Nil(t, v.Init(makeCtx()))
|
||||||
assert.Equal(t, "Deployments", v.Name())
|
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{
|
d := DaemonSet{
|
||||||
ResourceViewer: NewPortForwardExtender(
|
ResourceViewer: NewPortForwardExtender(
|
||||||
NewRestartExtender(
|
NewRestartExtender(
|
||||||
NewSetImageExtender(
|
NewImageExtender(
|
||||||
NewLogsExtender(NewBrowser(gvr), nil),
|
NewLogsExtender(NewBrowser(gvr), nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
d.SetBindKeysFn(d.bindKeys)
|
d.AddBindKeysFn(d.bindKeys)
|
||||||
d.GetTable().SetEnterFn(d.showPods)
|
d.GetTable().SetEnterFn(d.showPods)
|
||||||
d.GetTable().SetColorerFn(render.DaemonSet{}.ColorerFunc())
|
d.GetTable().SetColorerFn(render.DaemonSet{}.ColorerFunc())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ func TestDaemonSet(t *testing.T) {
|
||||||
|
|
||||||
assert.Nil(t, v.Init(makeCtx()))
|
assert.Nil(t, v.Init(makeCtx()))
|
||||||
assert.Equal(t, "DaemonSets", v.Name())
|
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),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
e.GetTable().SetColorerFn(render.Event{}.ColorerFunc())
|
e.GetTable().SetColorerFn(render.Event{}.ColorerFunc())
|
||||||
e.SetBindKeysFn(e.bindKeys)
|
e.AddBindKeysFn(e.bindKeys)
|
||||||
e.GetTable().SetSortCol(ageCol, true)
|
e.GetTable().SetSortCol(ageCol, true)
|
||||||
|
|
||||||
return &e
|
return &e
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ func execute(opts shellOpts) error {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
log.Debug().Msgf("Running command> %s %s", opts.binary, strings.Join(opts.args, " "))
|
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
|
var err error
|
||||||
if opts.background {
|
if opts.background {
|
||||||
|
|
@ -162,7 +162,6 @@ func oneShoot(opts shellOpts) (string, error) {
|
||||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, buff, buff
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, buff, buff
|
||||||
_, _ = cmd.Stdout.Write([]byte(opts.banner))
|
_, _ = cmd.Stdout.Write([]byte(opts.banner))
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
log.Debug().Msgf("RES %q", buff)
|
|
||||||
|
|
||||||
return strings.Trim(buff.String(), "\n"), err
|
return strings.Trim(buff.String(), "\n"), err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type Group struct {
|
||||||
func NewGroup(gvr client.GVR) ResourceViewer {
|
func NewGroup(gvr client.GVR) ResourceViewer {
|
||||||
g := Group{ResourceViewer: NewBrowser(gvr)}
|
g := Group{ResourceViewer: NewBrowser(gvr)}
|
||||||
g.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
g.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
||||||
g.SetBindKeysFn(g.bindKeys)
|
g.AddBindKeysFn(g.bindKeys)
|
||||||
g.SetContextFn(g.subjectCtx)
|
g.SetContextFn(g.subjectCtx)
|
||||||
|
|
||||||
return &g
|
return &g
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func NewHelm(gvr client.GVR) ResourceViewer {
|
||||||
c.GetTable().SetColorerFn(render.Helm{}.ColorerFunc())
|
c.GetTable().SetColorerFn(render.Helm{}.ColorerFunc())
|
||||||
c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
c.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||||
c.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
c.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||||
c.SetBindKeysFn(c.bindKeys)
|
c.AddBindKeysFn(c.bindKeys)
|
||||||
c.SetContextFn(c.chartContext)
|
c.SetContextFn(c.chartContext)
|
||||||
|
|
||||||
return &c
|
return &c
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ func TestK8sEnv(t *testing.T) {
|
||||||
assert.Equal(t, cl, env["CLUSTER"])
|
assert.Equal(t, cl, env["CLUSTER"])
|
||||||
assert.Equal(t, ctx, env["CONTEXT"])
|
assert.Equal(t, ctx, env["CONTEXT"])
|
||||||
assert.Equal(t, u, env["USER"])
|
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, cfg, env["KUBECONFIG"])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ func TestK9sEnv(t *testing.T) {
|
||||||
assert.Equal(t, cl, env["CLUSTER"])
|
assert.Equal(t, cl, env["CLUSTER"])
|
||||||
assert.Equal(t, ctx, env["CONTEXT"])
|
assert.Equal(t, ctx, env["CONTEXT"])
|
||||||
assert.Equal(t, u, env["USER"])
|
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, cfg, env["KUBECONFIG"])
|
||||||
assert.Equal(t, "fred", env["NAMESPACE"])
|
assert.Equal(t, "fred", env["NAMESPACE"])
|
||||||
assert.Equal(t, "blee", env["NAME"])
|
assert.Equal(t, "blee", env["NAME"])
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,17 @@ package view
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const setImageKey = "setImage"
|
const imageKey = "setImage"
|
||||||
|
|
||||||
// SetImageExtender adds set image extensions
|
|
||||||
type SetImageExtender struct {
|
|
||||||
ResourceViewer
|
|
||||||
}
|
|
||||||
|
|
||||||
type imageFormSpec struct {
|
type imageFormSpec struct {
|
||||||
name, dockerImage, newDockerImage string
|
name, dockerImage, newDockerImage string
|
||||||
|
|
@ -44,20 +40,28 @@ func (m *imageFormSpec) imageSpec() dao.ImageSpec {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSetImageExtender(r ResourceViewer) ResourceViewer {
|
// ImageExtender provides for overriding container images.
|
||||||
s := SetImageExtender{ResourceViewer: r}
|
type ImageExtender struct {
|
||||||
s.bindKeys(s.Actions())
|
ResourceViewer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewImageExtender(r ResourceViewer) ResourceViewer {
|
||||||
|
s := ImageExtender{ResourceViewer: r}
|
||||||
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
|
|
||||||
return &s
|
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{
|
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()
|
path := s.GetTable().GetSelectedItem()
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -65,63 +69,58 @@ func (s *SetImageExtender) setImageCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
|
||||||
s.Stop()
|
s.Stop()
|
||||||
defer s.Start()
|
defer s.Start()
|
||||||
s.showSetImageDialog(path)
|
s.showImageDialog(path)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SetImageExtender) showSetImageDialog(path string) {
|
func (s *ImageExtender) showImageDialog(path string) {
|
||||||
confirm := tview.NewModalForm("<Set image>", s.makeSetImageForm(path))
|
confirm := tview.NewModalForm("<Set image>", s.makeSetImageForm(path))
|
||||||
confirm.SetText(fmt.Sprintf("Set image %s %s", s.GVR(), path))
|
confirm.SetText(fmt.Sprintf("Set image %s %s", s.GVR(), path))
|
||||||
confirm.SetDoneFunc(func(int, string) {
|
confirm.SetDoneFunc(func(int, string) {
|
||||||
s.dismissDialog()
|
s.dismissDialog()
|
||||||
})
|
})
|
||||||
s.App().Content.AddPage(setImageKey, confirm, false, false)
|
s.App().Content.AddPage(imageKey, confirm, false, false)
|
||||||
s.App().Content.ShowPage(setImageKey)
|
s.App().Content.ShowPage(imageKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SetImageExtender) makeSetImageForm(sel string) *tview.Form {
|
func (s *ImageExtender) makeSetImageForm(sel string) *tview.Form {
|
||||||
f := s.makeStyledForm()
|
f := s.makeStyledForm()
|
||||||
podSpec, err := s.getPodSpec(sel)
|
podSpec, err := s.getPodSpec(sel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.App().Flash().Err(err)
|
s.App().Flash().Err(err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var formContainerLines []imageFormSpec
|
formContainerLines := make([]*imageFormSpec, len(podSpec.InitContainers)+len(podSpec.Containers))
|
||||||
for _, spec := range podSpec.InitContainers {
|
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 {
|
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 {
|
for i := range formContainerLines {
|
||||||
ctnCopy := ctn
|
ctn := formContainerLines[i]
|
||||||
f.AddInputField(ctn.name, ctn.dockerImage, 0, nil, func(changed string) {
|
f.AddInputField(ctn.name, ctn.dockerImage, 0, nil, func(changed string) {
|
||||||
ctnCopy.newDockerImage = changed
|
ctn.newDockerImage = changed
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
f.AddButton("OK", func() {
|
f.AddButton("OK", func() {
|
||||||
defer s.dismissDialog()
|
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
|
var imageSpecsModified dao.ImageSpecs
|
||||||
for _, v := range formContainerLines {
|
for _, v := range formContainerLines {
|
||||||
if v.modified() {
|
if v.modified() {
|
||||||
imageSpecsModified = append(imageSpecsModified, v.imageSpec())
|
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 {
|
if err := s.setImages(ctx, sel, imageSpecsModified); err != nil {
|
||||||
log.Error().Err(err).Msgf("PodSpec %s image update failed", sel)
|
log.Error().Err(err).Msgf("PodSpec %s image update failed", sel)
|
||||||
s.App().Flash().Err(err)
|
s.App().Flash().Err(err)
|
||||||
} else {
|
return
|
||||||
s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel)
|
|
||||||
}
|
}
|
||||||
|
s.App().Flash().Infof("Resource %s:%s image updated successfully", s.GVR(), sel)
|
||||||
})
|
})
|
||||||
f.AddButton("Cancel", func() {
|
f.AddButton("Cancel", func() {
|
||||||
s.dismissDialog()
|
s.dismissDialog()
|
||||||
|
|
@ -129,11 +128,11 @@ func (s *SetImageExtender) makeSetImageForm(sel string) *tview.Form {
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SetImageExtender) dismissDialog() {
|
func (s *ImageExtender) dismissDialog() {
|
||||||
s.App().Content.RemovePage(setImageKey)
|
s.App().Content.RemovePage(imageKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SetImageExtender) makeStyledForm() *tview.Form {
|
func (s *ImageExtender) makeStyledForm() *tview.Form {
|
||||||
f := tview.NewForm()
|
f := tview.NewForm()
|
||||||
f.SetItemPadding(0)
|
f.SetItemPadding(0)
|
||||||
f.SetButtonsAlign(tview.AlignCenter).
|
f.SetButtonsAlign(tview.AlignCenter).
|
||||||
|
|
@ -144,19 +143,20 @@ func (s *SetImageExtender) makeStyledForm() *tview.Form {
|
||||||
return f
|
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())
|
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
|
resourceWPodSpec, ok := res.(dao.ContainsPodSpec)
|
||||||
if !ok {
|
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)
|
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())
|
res, err := dao.AccessorFor(s.App().factory, s.GVR())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -54,7 +54,7 @@ func NewLog(gvr client.GVR, path, co string, prev bool) *Log {
|
||||||
Flex: tview.NewFlex(),
|
Flex: tview.NewFlex(),
|
||||||
model: model.NewLog(
|
model: model.NewLog(
|
||||||
gvr,
|
gvr,
|
||||||
buildLogOpts(path, co, prev, true, config.DefaultLoggerTailCount),
|
buildLogOpts(path, co, prev, false, config.DefaultLoggerTailCount),
|
||||||
flushTimeout,
|
flushTimeout,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@ func (l *Log) Init(ctx context.Context) (err error) {
|
||||||
func (l *Log) LogCleared() {
|
func (l *Log) LogCleared() {
|
||||||
l.app.QueueUpdateDraw(func() {
|
l.app.QueueUpdateDraw(func() {
|
||||||
l.logs.Clear()
|
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.
|
// LogChanged updates the logs.
|
||||||
func (l *Log) LogChanged(lines dao.LogItems) {
|
func (l *Log) LogChanged(lines [][]byte) {
|
||||||
l.app.QueueUpdateDraw(func() {
|
l.app.QueueUpdateDraw(func() {
|
||||||
l.Flush(lines)
|
l.Flush(lines)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// BufferCompleted indicates input was accepted.
|
||||||
func (l *Log) BufferChanged(s string) {
|
func (l *Log) BufferCompleted(s string) {
|
||||||
l.model.Filter(l.logs.cmdBuff.GetText())
|
l.model.Filter(l.logs.cmdBuff.GetText())
|
||||||
l.updateTitle()
|
l.updateTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferChanged indicates the buffer was changed.
|
||||||
|
func (l *Log) BufferChanged(s string) {}
|
||||||
|
|
||||||
// BufferActive indicates the buff activity changed.
|
// BufferActive indicates the buff activity changed.
|
||||||
func (l *Log) BufferActive(state bool, k model.BufferKind) {
|
func (l *Log) BufferActive(state bool, k model.BufferKind) {
|
||||||
l.app.BufferActive(state, k)
|
l.app.BufferActive(state, k)
|
||||||
|
|
@ -181,24 +184,40 @@ func (l *Log) Name() string { return logTitle }
|
||||||
|
|
||||||
func (l *Log) bindKeys() {
|
func (l *Log) bindKeys() {
|
||||||
l.logs.Actions().Set(ui.KeyActions{
|
l.logs.Actions().Set(ui.KeyActions{
|
||||||
ui.Key0: ui.NewKeyAction("all", l.sinceCmd(-1), true),
|
ui.Key0: ui.NewKeyAction("all", l.sinceCmd(-1), true),
|
||||||
ui.Key1: ui.NewKeyAction("1m", l.sinceCmd(60), true),
|
ui.Key1: ui.NewKeyAction("1m", l.sinceCmd(60), true),
|
||||||
ui.Key2: ui.NewKeyAction("5m", l.sinceCmd(5*60), true),
|
ui.Key2: ui.NewKeyAction("5m", l.sinceCmd(5*60), true),
|
||||||
ui.Key3: ui.NewKeyAction("15m", l.sinceCmd(15*60), true),
|
ui.Key3: ui.NewKeyAction("15m", l.sinceCmd(15*60), true),
|
||||||
ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true),
|
ui.Key4: ui.NewKeyAction("30m", l.sinceCmd(30*60), true),
|
||||||
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
ui.Key5: ui.NewKeyAction("1h", l.sinceCmd(60*60), true),
|
||||||
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
tcell.KeyEnter: ui.NewSharedKeyAction("Filter", l.filterCmd, false),
|
||||||
tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true),
|
tcell.KeyEscape: ui.NewKeyAction("Back", l.resetCmd, false),
|
||||||
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
|
tcell.KeyCtrlK: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
ui.KeyM: ui.NewKeyAction("Mark", l.markCmd, true),
|
||||||
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleAutoScrollCmd, true),
|
||||||
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
ui.KeyF: ui.NewKeyAction("Toggle FullScreen", l.toggleFullScreenCmd, true),
|
||||||
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.toggleTextWrapCmd, true),
|
ui.KeyT: ui.NewKeyAction("Toggle Timestamp", l.toggleTimestampCmd, true),
|
||||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
ui.KeyW: ui.NewKeyAction("Toggle Wrap", l.toggleTextWrapCmd, true),
|
||||||
ui.KeyC: ui.NewKeyAction("Copy", l.cpCmd, 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!)
|
// SendStrokes (testing only!)
|
||||||
func (l *Log) SendStrokes(s string) {
|
func (l *Log) SendStrokes(s string) {
|
||||||
l.app.Prompt().SendStrokes(s)
|
l.app.Prompt().SendStrokes(s)
|
||||||
|
|
@ -248,14 +267,13 @@ func (l *Log) Logs() *Details {
|
||||||
var EOL = []byte{'\n'}
|
var EOL = []byte{'\n'}
|
||||||
|
|
||||||
// Flush write logs to viewer.
|
// 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() {
|
if !l.indicator.AutoScroll() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ll := make([][]byte, len(lines))
|
|
||||||
lines.Render(l.Indicator().showTime, ll)
|
|
||||||
_, _ = l.ansiWriter.Write(EOL)
|
_, _ = 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")
|
log.Error().Err(err).Msgf("write logs failed")
|
||||||
}
|
}
|
||||||
l.logs.ScrollToEnd()
|
l.logs.ScrollToEnd()
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ func TestLogViewClear(t *testing.T) {
|
||||||
func TestLogTimestamp(t *testing.T) {
|
func TestLogTimestamp(t *testing.T) {
|
||||||
l := NewLog(client.NewGVR("test"), "fred/blee", "c1", false)
|
l := NewLog(client.NewGVR("test"), "fred/blee", "c1", false)
|
||||||
l.Init(makeContext())
|
l.Init(makeContext())
|
||||||
buff := dao.LogItems{
|
ii := dao.LogItems{
|
||||||
&dao.LogItem{
|
&dao.LogItem{
|
||||||
Pod: "fred/blee",
|
Pod: "fred/blee",
|
||||||
Container: "c1",
|
Container: "c1",
|
||||||
|
|
@ -61,10 +61,10 @@ func TestLogTimestamp(t *testing.T) {
|
||||||
}
|
}
|
||||||
var list logList
|
var list logList
|
||||||
l.GetModel().AddListener(&list)
|
l.GetModel().AddListener(&list)
|
||||||
l.GetModel().Set(buff)
|
l.GetModel().Set(ii)
|
||||||
l.SendKeys(ui.KeyT)
|
l.SendKeys(ui.KeyT)
|
||||||
l.Logs().Clear()
|
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, fmt.Sprintf("\n%-30s %s", "ttt", "fred/blee:c1 Testing 1, 2, 3"), l.Logs().GetText(true))
|
||||||
assert.Equal(t, 2, list.change)
|
assert.Equal(t, 2, list.change)
|
||||||
|
|
@ -99,11 +99,11 @@ type logList struct {
|
||||||
lines string
|
lines string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logList) LogChanged(ii dao.LogItems) {
|
func (l *logList) LogChanged(ll [][]byte) {
|
||||||
l.change++
|
l.change++
|
||||||
l.lines = ""
|
l.lines = ""
|
||||||
for _, i := range ii {
|
for _, line := range ll {
|
||||||
l.lines += string(i.Render(0, false))
|
l.lines += string(line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func (l *logList) LogCleared() { l.clear++ }
|
func (l *logList) LogCleared() { l.clear++ }
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func TestLog(t *testing.T) {
|
||||||
v.Flush(dao.LogItems{
|
v.Flush(dao.LogItems{
|
||||||
dao.NewLogItemFromString("blee"),
|
dao.NewLogItemFromString("blee"),
|
||||||
dao.NewLogItemFromString("bozo"),
|
dao.NewLogItemFromString("bozo"),
|
||||||
})
|
}.Lines(false))
|
||||||
|
|
||||||
assert.Equal(t, 29, len(v.Logs().GetText(true)))
|
assert.Equal(t, 29, len(v.Logs().GetText(true)))
|
||||||
}
|
}
|
||||||
|
|
@ -38,7 +38,7 @@ func BenchmarkLogFlush(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for n := 0; n < b.N; n++ {
|
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())
|
v.Init(makeContext())
|
||||||
|
|
||||||
app := makeApp()
|
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"
|
config.K9sDumpDir = "/tmp"
|
||||||
dir := filepath.Join(config.K9sDumpDir, app.Config.K9s.CurrentCluster)
|
dir := filepath.Join(config.K9sDumpDir, app.Config.K9s.CurrentCluster)
|
||||||
c1, _ := ioutil.ReadDir(dir)
|
c1, _ := ioutil.ReadDir(dir)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func NewLogsExtender(v ResourceViewer, f ContainerFunc) ResourceViewer {
|
||||||
ResourceViewer: v,
|
ResourceViewer: v,
|
||||||
containerFn: f,
|
containerFn: f,
|
||||||
}
|
}
|
||||||
l.bindKeys(l.Actions())
|
l.AddBindKeysFn(l.bindKeys)
|
||||||
|
|
||||||
return &l
|
return &l
|
||||||
}
|
}
|
||||||
|
|
@ -27,8 +27,8 @@ func NewLogsExtender(v ResourceViewer, f ContainerFunc) ResourceViewer {
|
||||||
// BindKeys injects new menu actions.
|
// BindKeys injects new menu actions.
|
||||||
func (l *LogsExtender) bindKeys(aa ui.KeyActions) {
|
func (l *LogsExtender) bindKeys(aa ui.KeyActions) {
|
||||||
aa.Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true),
|
ui.KeyL: ui.NewKeyAction("Logs", l.logsCmd(false), true),
|
||||||
ui.KeyShiftL: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true),
|
ui.KeyP: ui.NewKeyAction("Logs Previous", l.logsCmd(true), true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ func NewNode(gvr client.GVR) ResourceViewer {
|
||||||
n := Node{
|
n := Node{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
n.SetBindKeysFn(n.bindKeys)
|
n.AddBindKeysFn(n.bindKeys)
|
||||||
n.GetTable().SetEnterFn(n.showPods)
|
n.GetTable().SetEnterFn(n.showPods)
|
||||||
|
|
||||||
return &n
|
return &n
|
||||||
|
|
@ -49,7 +49,7 @@ func (n *Node) bindDangerousKeys(aa ui.KeyActions) {
|
||||||
func (n *Node) bindKeys(aa ui.KeyActions) {
|
func (n *Node) bindKeys(aa ui.KeyActions) {
|
||||||
aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlD)
|
aa.Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlD)
|
||||||
|
|
||||||
if !n.App().Config.K9s.GetReadOnly() {
|
if !n.App().Config.K9s.IsReadOnly() {
|
||||||
n.bindDangerousKeys(aa)
|
n.bindDangerousKeys(aa)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func NewNamespace(gvr client.GVR) ResourceViewer {
|
||||||
n.GetTable().SetDecorateFn(n.decorate)
|
n.GetTable().SetDecorateFn(n.decorate)
|
||||||
n.GetTable().SetColorerFn(render.Namespace{}.ColorerFunc())
|
n.GetTable().SetColorerFn(render.Namespace{}.ColorerFunc())
|
||||||
n.GetTable().SetEnterFn(n.switchNs)
|
n.GetTable().SetEnterFn(n.switchNs)
|
||||||
n.SetBindKeysFn(n.bindKeys)
|
n.AddBindKeysFn(n.bindKeys)
|
||||||
|
|
||||||
return &n
|
return &n
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type OpenFaas struct {
|
||||||
// NewOpenFaas returns a new viewer.
|
// NewOpenFaas returns a new viewer.
|
||||||
func NewOpenFaas(gvr client.GVR) ResourceViewer {
|
func NewOpenFaas(gvr client.GVR) ResourceViewer {
|
||||||
o := OpenFaas{ResourceViewer: NewBrowser(gvr)}
|
o := OpenFaas{ResourceViewer: NewBrowser(gvr)}
|
||||||
o.SetBindKeysFn(o.bindKeys)
|
o.AddBindKeysFn(o.bindKeys)
|
||||||
o.GetTable().SetEnterFn(o.showPods)
|
o.GetTable().SetEnterFn(o.showPods)
|
||||||
o.GetTable().SetColorerFn(render.OpenFaas{}.ColorerFunc())
|
o.GetTable().SetColorerFn(render.OpenFaas{}.ColorerFunc())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func NewPortForward(gvr client.GVR) ResourceViewer {
|
||||||
p.GetTable().SetColorerFn(render.PortForward{}.ColorerFunc())
|
p.GetTable().SetColorerFn(render.PortForward{}.ColorerFunc())
|
||||||
p.GetTable().SetSortCol(ageCol, true)
|
p.GetTable().SetSortCol(ageCol, true)
|
||||||
p.SetContextFn(p.portForwardContext)
|
p.SetContextFn(p.portForwardContext)
|
||||||
p.SetBindKeysFn(p.bindKeys)
|
p.AddBindKeysFn(p.bindKeys)
|
||||||
|
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
)
|
)
|
||||||
|
|
@ -117,7 +118,7 @@ func extractPort(p string) string {
|
||||||
func extractContainer(p string) string {
|
func extractContainer(p string) string {
|
||||||
tokens := strings.Split(p, ":")
|
tokens := strings.Split(p, ":")
|
||||||
if len(tokens) != 2 {
|
if len(tokens) != 2 {
|
||||||
return "n/a"
|
return render.NAValue
|
||||||
}
|
}
|
||||||
|
|
||||||
co, _ := client.Namespaced(tokens[0])
|
co, _ := client.Namespaced(tokens[0])
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ type PortForwardExtender struct {
|
||||||
|
|
||||||
// NewPortForwardExtender returns a new extender.
|
// NewPortForwardExtender returns a new extender.
|
||||||
func NewPortForwardExtender(r ResourceViewer) ResourceViewer {
|
func NewPortForwardExtender(r ResourceViewer) ResourceViewer {
|
||||||
s := PortForwardExtender{ResourceViewer: r}
|
p := PortForwardExtender{ResourceViewer: r}
|
||||||
s.bindKeys(s.Actions())
|
p.AddBindKeysFn(p.bindKeys)
|
||||||
|
|
||||||
return &s
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PortForwardExtender) bindKeys(aa ui.KeyActions) {
|
func (p *PortForwardExtender) bindKeys(aa ui.KeyActions) {
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,11 @@ type Pod struct {
|
||||||
func NewPod(gvr client.GVR) ResourceViewer {
|
func NewPod(gvr client.GVR) ResourceViewer {
|
||||||
p := Pod{}
|
p := Pod{}
|
||||||
p.ResourceViewer = NewPortForwardExtender(
|
p.ResourceViewer = NewPortForwardExtender(
|
||||||
NewSetImageExtender(
|
NewImageExtender(
|
||||||
NewLogsExtender(NewBrowser(gvr), p.selectedContainer),
|
NewLogsExtender(NewBrowser(gvr), p.selectedContainer),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
p.SetBindKeysFn(p.bindKeys)
|
p.AddBindKeysFn(p.bindKeys)
|
||||||
p.GetTable().SetEnterFn(p.showContainers)
|
p.GetTable().SetEnterFn(p.showContainers)
|
||||||
p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())
|
p.GetTable().SetColorerFn(render.Pod{}.ColorerFunc())
|
||||||
p.GetTable().SetDecorateFn(p.portForwardIndicator)
|
p.GetTable().SetDecorateFn(p.portForwardIndicator)
|
||||||
|
|
@ -63,7 +63,7 @@ func (p *Pod) bindDangerousKeys(aa ui.KeyActions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Pod) bindKeys(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)
|
p.bindDangerousKeys(aa)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -351,7 +351,6 @@ func podIsRunning(f dao.Factory, path string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
var re render.Pod
|
var re render.Pod
|
||||||
log.Debug().Msgf("Phase %#v", re.Phase(po))
|
|
||||||
return re.Phase(po) == render.Running
|
return re.Phase(po) == render.Running
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func NewPolicy(app *App, subject, name string) *Policy {
|
||||||
subjectName: name,
|
subjectName: name,
|
||||||
}
|
}
|
||||||
p.GetTable().SetColorerFn(render.Policy{}.ColorerFunc())
|
p.GetTable().SetColorerFn(render.Policy{}.ColorerFunc())
|
||||||
p.SetBindKeysFn(p.bindKeys)
|
p.AddBindKeysFn(p.bindKeys)
|
||||||
p.GetTable().SetSortCol(nameCol, false)
|
p.GetTable().SetSortCol(nameCol, false)
|
||||||
p.SetContextFn(p.subjectCtx)
|
p.SetContextFn(p.subjectCtx)
|
||||||
p.GetTable().SetEnterFn(blankEnterFn)
|
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().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||||
p.GetTable().SetSortCol("SCORE%", true)
|
p.GetTable().SetSortCol("SCORE%", true)
|
||||||
p.GetTable().SetDecorateFn(p.decorateRows)
|
p.GetTable().SetDecorateFn(p.decorateRows)
|
||||||
p.SetBindKeysFn(p.bindKeys)
|
p.AddBindKeysFn(p.bindKeys)
|
||||||
|
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -264,8 +264,8 @@ func (p *Pulse) SetInstance(string) {}
|
||||||
// SetEnvFn sets the custom environment function.
|
// SetEnvFn sets the custom environment function.
|
||||||
func (p *Pulse) SetEnvFn(EnvFunc) {}
|
func (p *Pulse) SetEnvFn(EnvFunc) {}
|
||||||
|
|
||||||
// SetBindKeysFn sets up extra key bindings.
|
// AddBindKeysFn sets up extra key bindings.
|
||||||
func (p *Pulse) SetBindKeysFn(BindKeysFunc) {}
|
func (p *Pulse) AddBindKeysFn(BindKeysFunc) {}
|
||||||
|
|
||||||
// SetContextFn sets custom context.
|
// SetContextFn sets custom context.
|
||||||
func (p *Pulse) SetContextFn(ContextFunc) {}
|
func (p *Pulse) SetContextFn(ContextFunc) {}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func NewPersistentVolumeClaim(gvr client.GVR) ResourceViewer {
|
||||||
v := PersistentVolumeClaim{
|
v := PersistentVolumeClaim{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
v.SetBindKeysFn(v.bindKeys)
|
v.AddBindKeysFn(v.bindKeys)
|
||||||
v.GetTable().SetColorerFn(render.PersistentVolumeClaim{}.ColorerFunc())
|
v.GetTable().SetColorerFn(render.PersistentVolumeClaim{}.ColorerFunc())
|
||||||
|
|
||||||
return &v
|
return &v
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func NewRbac(gvr client.GVR) ResourceViewer {
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
r.GetTable().SetColorerFn(render.Rbac{}.ColorerFunc())
|
r.GetTable().SetColorerFn(render.Rbac{}.ColorerFunc())
|
||||||
r.SetBindKeysFn(r.bindKeys)
|
r.AddBindKeysFn(r.bindKeys)
|
||||||
r.GetTable().SetSortCol("APIGROUP", true)
|
r.GetTable().SetSortCol("APIGROUP", true)
|
||||||
r.GetTable().SetEnterFn(blankEnterFn)
|
r.GetTable().SetEnterFn(blankEnterFn)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func NewReference(gvr client.GVR) ResourceViewer {
|
||||||
r.GetTable().SetColorerFn(render.Reference{}.ColorerFunc())
|
r.GetTable().SetColorerFn(render.Reference{}.ColorerFunc())
|
||||||
r.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
r.GetTable().SetBorderFocusColor(tcell.ColorMediumSpringGreen)
|
||||||
r.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
r.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
|
||||||
r.SetBindKeysFn(r.bindKeys)
|
r.AddBindKeysFn(r.bindKeys)
|
||||||
|
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,16 @@ type RestartExtender struct {
|
||||||
// NewRestartExtender returns a new extender.
|
// NewRestartExtender returns a new extender.
|
||||||
func NewRestartExtender(v ResourceViewer) ResourceViewer {
|
func NewRestartExtender(v ResourceViewer) ResourceViewer {
|
||||||
r := RestartExtender{ResourceViewer: v}
|
r := RestartExtender{ResourceViewer: v}
|
||||||
r.bindKeys(v.Actions())
|
v.AddBindKeysFn(r.bindKeys)
|
||||||
|
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindKeys creates additional menu actions.
|
// BindKeys creates additional menu actions.
|
||||||
func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
||||||
|
if r.App().Config.K9s.IsReadOnly() {
|
||||||
|
return
|
||||||
|
}
|
||||||
aa.Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func NewReplicaSet(gvr client.GVR) ResourceViewer {
|
||||||
r := ReplicaSet{
|
r := ReplicaSet{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
r.SetBindKeysFn(r.bindKeys)
|
r.AddBindKeysFn(r.bindKeys)
|
||||||
r.GetTable().SetEnterFn(r.showPods)
|
r.GetTable().SetEnterFn(r.showPods)
|
||||||
r.GetTable().SetColorerFn(render.ReplicaSet{}.ColorerFunc())
|
r.GetTable().SetColorerFn(render.ReplicaSet{}.ColorerFunc())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ func NewServiceAccount(gvr client.GVR) ResourceViewer {
|
||||||
s := ServiceAccount{
|
s := ServiceAccount{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
s.SetBindKeysFn(s.bindKeys)
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -315,7 +315,10 @@ func (s *Sanitizer) SetEnvFn(EnvFunc) {}
|
||||||
func (s *Sanitizer) Refresh() {}
|
func (s *Sanitizer) Refresh() {}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// 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()))
|
s.update(s.filter(s.model.Peek()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,8 +363,8 @@ func (s *Sanitizer) Stop() {
|
||||||
s.CmdBuff().RemoveListener(s)
|
s.CmdBuff().RemoveListener(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBindKeysFn sets up extra key bindings.
|
// AddBindKeysFn sets up extra key bindings.
|
||||||
func (s *Sanitizer) SetBindKeysFn(BindKeysFunc) {}
|
func (s *Sanitizer) AddBindKeysFn(BindKeysFunc) {}
|
||||||
|
|
||||||
// SetContextFn sets custom context.
|
// SetContextFn sets custom context.
|
||||||
func (s *Sanitizer) SetContextFn(f ContextFunc) {
|
func (s *Sanitizer) SetContextFn(f ContextFunc) {
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,15 @@ type ScaleExtender struct {
|
||||||
// NewScaleExtender returns a new extender.
|
// NewScaleExtender returns a new extender.
|
||||||
func NewScaleExtender(r ResourceViewer) ResourceViewer {
|
func NewScaleExtender(r ResourceViewer) ResourceViewer {
|
||||||
s := ScaleExtender{ResourceViewer: r}
|
s := ScaleExtender{ResourceViewer: r}
|
||||||
s.bindKeys(s.Actions())
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ScaleExtender) bindKeys(aa ui.KeyActions) {
|
func (s *ScaleExtender) bindKeys(aa ui.KeyActions) {
|
||||||
|
if s.App().Config.K9s.IsReadOnly() {
|
||||||
|
return
|
||||||
|
}
|
||||||
aa.Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
ui.KeyS: ui.NewKeyAction("Scale", s.scaleCmd, true),
|
ui.KeyS: ui.NewKeyAction("Scale", s.scaleCmd, true),
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ func NewSecret(gvr client.GVR) ResourceViewer {
|
||||||
s := Secret{
|
s := Secret{
|
||||||
ResourceViewer: NewBrowser(gvr),
|
ResourceViewer: NewBrowser(gvr),
|
||||||
}
|
}
|
||||||
s.SetBindKeysFn(s.bindKeys)
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,14 +21,14 @@ func NewStatefulSet(gvr client.GVR) ResourceViewer {
|
||||||
ResourceViewer: NewPortForwardExtender(
|
ResourceViewer: NewPortForwardExtender(
|
||||||
NewRestartExtender(
|
NewRestartExtender(
|
||||||
NewScaleExtender(
|
NewScaleExtender(
|
||||||
NewSetImageExtender(
|
NewImageExtender(
|
||||||
NewLogsExtender(NewBrowser(gvr), nil),
|
NewLogsExtender(NewBrowser(gvr), nil),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
s.SetBindKeysFn(s.bindKeys)
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
s.GetTable().SetEnterFn(s.showPods)
|
s.GetTable().SetEnterFn(s.showPods)
|
||||||
s.GetTable().SetColorerFn(render.StatefulSet{}.ColorerFunc())
|
s.GetTable().SetColorerFn(render.StatefulSet{}.ColorerFunc())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func NewService(gvr client.GVR) ResourceViewer {
|
||||||
NewLogsExtender(NewBrowser(gvr), nil),
|
NewLogsExtender(NewBrowser(gvr), nil),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
s.SetBindKeysFn(s.bindKeys)
|
s.AddBindKeysFn(s.bindKeys)
|
||||||
s.GetTable().SetEnterFn(s.showPods)
|
s.GetTable().SetEnterFn(s.showPods)
|
||||||
|
|
||||||
return &s
|
return &s
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ type Table struct {
|
||||||
app *App
|
app *App
|
||||||
enterFn EnterFunc
|
enterFn EnterFunc
|
||||||
envFn EnvFunc
|
envFn EnvFunc
|
||||||
bindKeysFn BindKeysFunc
|
bindKeysFn []BindKeysFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTable returns a new viewer.
|
// NewTable returns a new viewer.
|
||||||
|
|
@ -73,8 +73,10 @@ func (t *Table) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
// Name returns the table name.
|
// Name returns the table name.
|
||||||
func (t *Table) Name() string { return t.GVR().R() }
|
func (t *Table) Name() string { return t.GVR().R() }
|
||||||
|
|
||||||
// SetBindKeysFn adds additional key bindings.
|
// AddBindKeysFn adds additional key bindings.
|
||||||
func (t *Table) SetBindKeysFn(f BindKeysFunc) { t.bindKeysFn = f }
|
func (t *Table) AddBindKeysFn(f BindKeysFunc) {
|
||||||
|
t.bindKeysFn = append(t.bindKeysFn, f)
|
||||||
|
}
|
||||||
|
|
||||||
// SetEnvFn sets a function to pull viewer env vars for plugins.
|
// SetEnvFn sets a function to pull viewer env vars for plugins.
|
||||||
func (t *Table) SetEnvFn(f EnvFunc) { t.envFn = f }
|
func (t *Table) SetEnvFn(f EnvFunc) { t.envFn = f }
|
||||||
|
|
@ -125,11 +127,14 @@ func (t *Table) SetEnterFn(f EnterFunc) {
|
||||||
// SetExtraActionsFn specifies custom keyboard behavior.
|
// SetExtraActionsFn specifies custom keyboard behavior.
|
||||||
func (t *Table) SetExtraActionsFn(BoostActionsFunc) {}
|
func (t *Table) SetExtraActionsFn(BoostActionsFunc) {}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// BufferCompleted indicates input was accepted.
|
||||||
func (t *Table) BufferChanged(s string) {
|
func (t *Table) BufferCompleted(s string) {
|
||||||
t.Filter(s)
|
t.Filter(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BufferChanged indicates the buffer was changed.
|
||||||
|
func (t *Table) BufferChanged(s string) {}
|
||||||
|
|
||||||
// BufferActive indicates the buff activity changed.
|
// BufferActive indicates the buff activity changed.
|
||||||
func (t *Table) BufferActive(state bool, k model.BufferKind) {
|
func (t *Table) BufferActive(state bool, k model.BufferKind) {
|
||||||
t.app.BufferActive(state, k)
|
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 provision a custom context.
|
||||||
SetContextFn(ContextFunc)
|
SetContextFn(ContextFunc)
|
||||||
|
|
||||||
// SetBindKeys provision additional key bindings.
|
// AddBindKeys provision additional key bindings.
|
||||||
SetBindKeysFn(BindKeysFunc)
|
AddBindKeysFn(BindKeysFunc)
|
||||||
|
|
||||||
// SetInstance sets a parent FQN
|
// SetInstance sets a parent FQN
|
||||||
SetInstance(string)
|
SetInstance(string)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ type User struct {
|
||||||
func NewUser(gvr client.GVR) ResourceViewer {
|
func NewUser(gvr client.GVR) ResourceViewer {
|
||||||
u := User{ResourceViewer: NewBrowser(gvr)}
|
u := User{ResourceViewer: NewBrowser(gvr)}
|
||||||
u.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
u.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
|
||||||
u.SetBindKeysFn(u.bindKeys)
|
u.AddBindKeysFn(u.bindKeys)
|
||||||
u.SetContextFn(u.subjectCtx)
|
u.SetContextFn(u.subjectCtx)
|
||||||
|
|
||||||
return &u
|
return &u
|
||||||
|
|
|
||||||
|
|
@ -162,12 +162,12 @@ func (x *Xray) refreshActions() {
|
||||||
x.Actions().Delete(tcell.KeyEnter)
|
x.Actions().Delete(tcell.KeyEnter)
|
||||||
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
||||||
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), 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":
|
case "v1/pods":
|
||||||
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
||||||
aa[ui.KeyA] = ui.NewKeyAction("Attach", x.attachCmd, true)
|
aa[ui.KeyA] = ui.NewKeyAction("Attach", x.attachCmd, true)
|
||||||
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), 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)
|
x.Actions().Add(aa)
|
||||||
}
|
}
|
||||||
|
|
@ -545,11 +545,14 @@ func (x *Xray) SetEnvFn(EnvFunc) {}
|
||||||
// Refresh updates the view
|
// Refresh updates the view
|
||||||
func (x *Xray) Refresh() {}
|
func (x *Xray) Refresh() {}
|
||||||
|
|
||||||
// BufferChanged indicates the buffer was changed.
|
// BufferCompleted indicates the buffer was changed.
|
||||||
func (x *Xray) BufferChanged(s string) {
|
func (x *Xray) BufferCompleted(s string) {
|
||||||
x.update(x.filter(x.model.Peek()))
|
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.
|
// BufferActive indicates the buff activity changed.
|
||||||
func (x *Xray) BufferActive(state bool, k model.BufferKind) {
|
func (x *Xray) BufferActive(state bool, k model.BufferKind) {
|
||||||
x.app.BufferActive(state, k)
|
x.app.BufferActive(state, k)
|
||||||
|
|
@ -589,7 +592,7 @@ func (x *Xray) Stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetBindKeysFn sets up extra key bindings.
|
// SetBindKeysFn sets up extra key bindings.
|
||||||
func (x *Xray) SetBindKeysFn(BindKeysFunc) {}
|
func (x *Xray) AddBindKeysFn(BindKeysFunc) {}
|
||||||
|
|
||||||
// SetContextFn sets custom context.
|
// SetContextFn sets custom context.
|
||||||
func (x *Xray) SetContextFn(ContextFunc) {}
|
func (x *Xray) SetContextFn(ContextFunc) {}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue