parent
ee01ae7242
commit
4c222f80ed
|
|
@ -0,0 +1,29 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.10.3
|
||||||
|
|
||||||
|
## 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 is as ever very much noticed and appreciated!
|
||||||
|
|
||||||
|
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Change Logs
|
||||||
|
|
||||||
|
Maintenance release!
|
||||||
|
|
||||||
|
Thank you all for kicking the tires on these new drops and in making sure we get back to nominal quickly. You guys ROCK!!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Bugs/Features
|
||||||
|
|
||||||
|
* [Issue #455](https://github.com/derailed/k9s/issues/455)
|
||||||
|
* [Issue #454](https://github.com/derailed/k9s/issues/454)
|
||||||
|
* [Issue #453](https://github.com/derailed/k9s/issues/453)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
{
|
||||||
|
"apiVersion": "apiextensions.k8s.io/v1",
|
||||||
|
"kind": "CustomResourceDefinition",
|
||||||
|
"metadata": {
|
||||||
|
"annotations": {
|
||||||
|
"helm.sh/resource-policy": "keep",
|
||||||
|
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"annotations\":{\"helm.sh/resource-policy\":\"keep\"},\"labels\":{\"app\":\"istio-pilot\",\"chart\":\"istio\",\"heritage\":\"Tiller\",\"release\":\"istio\"},\"name\":\"destinationrules.networking.istio.io\"},\"spec\":{\"additionalPrinterColumns\":[{\"JSONPath\":\".spec.host\",\"description\":\"The name of a service from the service registry\",\"name\":\"Host\",\"type\":\"string\"},{\"JSONPath\":\".metadata.creationTimestamp\",\"description\":\"CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\\n\\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata\",\"name\":\"Age\",\"type\":\"date\"}],\"group\":\"networking.istio.io\",\"names\":{\"categories\":[\"istio-io\",\"networking-istio-io\"],\"kind\":\"DestinationRule\",\"listKind\":\"DestinationRuleList\",\"plural\":\"destinationrules\",\"shortNames\":[\"dr\"],\"singular\":\"destinationrule\"},\"scope\":\"Namespaced\",\"version\":\"v1alpha3\"}}\n"
|
||||||
|
},
|
||||||
|
"creationTimestamp": "2019-12-30T16:13:02Z",
|
||||||
|
"generation": 1,
|
||||||
|
"labels": {
|
||||||
|
"app": "istio-pilot",
|
||||||
|
"chart": "istio",
|
||||||
|
"heritage": "Tiller",
|
||||||
|
"release": "istio"
|
||||||
|
},
|
||||||
|
"name": "destinationrules.networking.istio.io",
|
||||||
|
"resourceVersion": "2773373",
|
||||||
|
"selfLink": "/apis/apiextensions.k8s.io/v1/customresourcedefinitions/destinationrules.networking.istio.io",
|
||||||
|
"uid": "123a30f8-8fcf-44b5-84b7-35f8c7869828"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"conversion": {
|
||||||
|
"strategy": "None"
|
||||||
|
},
|
||||||
|
"group": "networking.istio.io",
|
||||||
|
"version": "v1alpha3",
|
||||||
|
"names": {
|
||||||
|
"categories": [
|
||||||
|
"istio-io",
|
||||||
|
"networking-istio-io"
|
||||||
|
],
|
||||||
|
"kind": "DestinationRule",
|
||||||
|
"listKind": "DestinationRuleList",
|
||||||
|
"plural": "destinationrules",
|
||||||
|
"shortNames": [
|
||||||
|
"dr"
|
||||||
|
],
|
||||||
|
"singular": "destinationrule"
|
||||||
|
},
|
||||||
|
"preserveUnknownFields": true,
|
||||||
|
"scope": "Namespaced",
|
||||||
|
"versions": [
|
||||||
|
{
|
||||||
|
"additionalPrinterColumns": [
|
||||||
|
{
|
||||||
|
"description": "The name of a service from the service registry",
|
||||||
|
"jsonPath": ".spec.host",
|
||||||
|
"name": "Host",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata",
|
||||||
|
"jsonPath": ".metadata.creationTimestamp",
|
||||||
|
"name": "Age",
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "v1alpha3",
|
||||||
|
"served": true,
|
||||||
|
"storage": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"acceptedNames": {
|
||||||
|
"categories": [
|
||||||
|
"istio-io",
|
||||||
|
"networking-istio-io"
|
||||||
|
],
|
||||||
|
"kind": "DestinationRule",
|
||||||
|
"listKind": "DestinationRuleList",
|
||||||
|
"plural": "destinationrules",
|
||||||
|
"shortNames": [
|
||||||
|
"dr"
|
||||||
|
],
|
||||||
|
"singular": "destinationrule"
|
||||||
|
},
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"lastTransitionTime": "2019-12-30T16:13:02Z",
|
||||||
|
"message": "no conflicts found",
|
||||||
|
"reason": "NoConflicts",
|
||||||
|
"status": "True",
|
||||||
|
"type": "NamesAccepted"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lastTransitionTime": "2019-12-30T16:13:02Z",
|
||||||
|
"message": "the initial names have been accepted",
|
||||||
|
"reason": "InitialNamesAccepted",
|
||||||
|
"status": "True",
|
||||||
|
"type": "Established"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storedVersions": [
|
||||||
|
"v1alpha3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
|
@ -27,6 +28,7 @@ var _ Scalable = &Deployment{}
|
||||||
|
|
||||||
// Scale a Deployment.
|
// Scale a Deployment.
|
||||||
func (d *Deployment) Scale(path string, replicas int32) error {
|
func (d *Deployment) Scale(path string, replicas int32) error {
|
||||||
|
log.Debug().Msgf("SCALING DEPLOYMENT!! %q:%d", path, replicas)
|
||||||
ns, n := client.Namespaced(path)
|
ns, n := client.Namespaced(path)
|
||||||
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
|
scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -218,7 +218,7 @@ func extractMeta(o runtime.Object) (metav1.APIResource, []error) {
|
||||||
|
|
||||||
crd, ok := o.(*unstructured.Unstructured)
|
crd, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return m, append(errs, fmt.Errorf("Expected CustomResourceDefinition, but got %T", o))
|
return m, append(errs, fmt.Errorf("Expected Unstructured, but got %T", o))
|
||||||
}
|
}
|
||||||
|
|
||||||
var spec map[string]interface{}
|
var spec map[string]interface{}
|
||||||
|
|
@ -254,6 +254,7 @@ func extractSlice(m map[string]interface{}, n string, errs []error) ([]string, [
|
||||||
if m[n] == nil {
|
if m[n] == nil {
|
||||||
return nil, errs
|
return nil, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
s, ok := m[n].([]string)
|
s, ok := m[n].([]string)
|
||||||
if ok {
|
if ok {
|
||||||
return s, errs
|
return s, errs
|
||||||
|
|
@ -268,10 +269,11 @@ func extractSlice(m map[string]interface{}, n string, errs []error) ([]string, [
|
||||||
for i, name := range ii {
|
for i, name := range ii {
|
||||||
ss[i], ok = name.(string)
|
ss[i], ok = name.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return s, append(errs, fmt.Errorf("expecting string shortnames"))
|
return ss, append(errs, fmt.Errorf("expecting string shortnames"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s, errs
|
|
||||||
|
return ss, errs
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractStr(m map[string]interface{}, n string, errs []error) (string, []error) {
|
func extractStr(m map[string]interface{}, n string, errs []error) (string, []error) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractMeta(t *testing.T) {
|
||||||
|
c := load(t, "dr")
|
||||||
|
m, ee := extractMeta(c)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, len(ee))
|
||||||
|
assert.Equal(t, "destinationrules", m.Name)
|
||||||
|
assert.Equal(t, "destinationrule", m.SingularName)
|
||||||
|
assert.Equal(t, "DestinationRule", m.Kind)
|
||||||
|
assert.Equal(t, "networking.istio.io", m.Group)
|
||||||
|
assert.Equal(t, "v1alpha3", m.Version)
|
||||||
|
assert.Equal(t, true, m.Namespaced)
|
||||||
|
assert.Equal(t, []string{"dr"}, m.ShortNames)
|
||||||
|
var vv metav1.Verbs
|
||||||
|
assert.Equal(t, vv, m.Verbs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractSlice(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
m map[string]interface{}
|
||||||
|
n string
|
||||||
|
nn []string
|
||||||
|
ee []error
|
||||||
|
}{
|
||||||
|
"plain": {
|
||||||
|
m: map[string]interface{}{"shortNames": []string{"a", "b", "c"}},
|
||||||
|
n: "shortNames",
|
||||||
|
nn: []string{"a", "b", "c"},
|
||||||
|
},
|
||||||
|
"empty": {
|
||||||
|
m: map[string]interface{}{},
|
||||||
|
n: "shortNames",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ee []error
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
ss, e := extractSlice(u.m, u.n, ee)
|
||||||
|
assert.Equal(t, u.ee, e)
|
||||||
|
assert.Equal(t, u.nn, ss)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtractString(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
m map[string]interface{}
|
||||||
|
n string
|
||||||
|
s string
|
||||||
|
ee []error
|
||||||
|
}{
|
||||||
|
"plain": {
|
||||||
|
m: map[string]interface{}{"blee": "fred"},
|
||||||
|
n: "blee",
|
||||||
|
s: "fred",
|
||||||
|
},
|
||||||
|
"missing": {
|
||||||
|
m: map[string]interface{}{},
|
||||||
|
n: "blee",
|
||||||
|
ee: []error{fmt.Errorf("failed to extract string blee")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ee []error
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
as, ae := extractStr(u.m, u.n, ee)
|
||||||
|
assert.Equal(t, u.ee, ae)
|
||||||
|
assert.Equal(t, u.s, as)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
func load(t *testing.T, n string) *unstructured.Unstructured {
|
||||||
|
raw, err := ioutil.ReadFile(fmt.Sprintf("assets/%s.json", n))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
var o unstructured.Unstructured
|
||||||
|
err = json.Unmarshal(raw, &o)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
refreshRate = 1 * time.Second
|
refreshRate = 2 * time.Second
|
||||||
noDataCount = 2
|
noDataCount = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,19 +117,11 @@ func (rr RowEvents) Upsert(e RowEvent) RowEvents {
|
||||||
|
|
||||||
// Delete removes an element by id.
|
// Delete removes an element by id.
|
||||||
func (rr RowEvents) Delete(id string) RowEvents {
|
func (rr RowEvents) Delete(id string) RowEvents {
|
||||||
idx, ok := rr.FindIndex(id)
|
victim, ok := rr.FindIndex(id)
|
||||||
if !ok {
|
if !ok {
|
||||||
return rr
|
return rr
|
||||||
}
|
}
|
||||||
|
return append(rr[0:victim], rr[victim+1:]...)
|
||||||
if idx == 0 {
|
|
||||||
return rr[1:]
|
|
||||||
}
|
|
||||||
if idx == len(rr)-1 {
|
|
||||||
return rr[:len(rr)-1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(rr[:idx], rr[idx+1:]...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear delete all row events
|
// Clear delete all row events
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,58 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestRowEventsDelete(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
re render.RowEvents
|
||||||
|
id string
|
||||||
|
e render.RowEvents
|
||||||
|
}{
|
||||||
|
"first": {
|
||||||
|
re: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
id: "A",
|
||||||
|
e: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"middle": {
|
||||||
|
re: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
id: "B",
|
||||||
|
e: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"last": {
|
||||||
|
re: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
id: "C",
|
||||||
|
e: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, u.re.Delete(u.id))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSort(t *testing.T) {
|
func TestSort(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
re render.RowEvents
|
re render.RowEvents
|
||||||
|
|
@ -32,10 +84,10 @@ func TestSort(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
uc := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
uc.re.Sort("", uc.col, uc.asc)
|
u.re.Sort("", u.col, u.asc)
|
||||||
assert.Equal(t, uc.e, uc.re)
|
assert.Equal(t, u.e, u.re)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -52,9 +104,9 @@ func TestDefaultColorer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
uc := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
assert.Equal(t, uc.e, render.DefaultColorer("", render.RowEvent{}))
|
assert.Equal(t, u.e, render.DefaultColorer("", render.RowEvent{}))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import "sync"
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
// TableData tracks a K8s resource for tabular display.
|
// TableData tracks a K8s resource for tabular display.
|
||||||
type TableData struct {
|
type TableData struct {
|
||||||
|
|
@ -40,6 +44,7 @@ func (t *TableData) Update(rows Rows) {
|
||||||
t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row))
|
t.RowEvents = append(t.RowEvents, NewRowEvent(EventAdd, row))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if index, ok := t.RowEvents.FindIndex(row.ID); ok {
|
if index, ok := t.RowEvents.FindIndex(row.ID); ok {
|
||||||
delta := NewDeltaRow(t.RowEvents[index].Row, row, t.Header.HasAge())
|
delta := NewDeltaRow(t.RowEvents[index].Row, row, t.Header.HasAge())
|
||||||
if delta.IsBlank() {
|
if delta.IsBlank() {
|
||||||
|
|
@ -60,6 +65,7 @@ func (t *TableData) Update(rows Rows) {
|
||||||
|
|
||||||
// Delete delete items in cache that are no longer valid.
|
// Delete delete items in cache that are no longer valid.
|
||||||
func (t *TableData) Delete(newKeys []string) {
|
func (t *TableData) Delete(newKeys []string) {
|
||||||
|
var victims []string
|
||||||
for _, re := range t.RowEvents {
|
for _, re := range t.RowEvents {
|
||||||
var found bool
|
var found bool
|
||||||
for i, key := range newKeys {
|
for i, key := range newKeys {
|
||||||
|
|
@ -70,9 +76,14 @@ func (t *TableData) Delete(newKeys []string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
t.RowEvents = t.RowEvents.Delete(re.Row.ID)
|
victims = append(victims, re.Row.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, id := range victims {
|
||||||
|
log.Debug().Msgf("Deleting %s", id)
|
||||||
|
t.RowEvents = t.RowEvents.Delete(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff checks if two tables are equal.
|
// Diff checks if two tables are equal.
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package render_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/render"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTableDataDelete(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
re render.RowEvents
|
||||||
|
kk []string
|
||||||
|
e render.RowEvents
|
||||||
|
}{
|
||||||
|
"ordered": {
|
||||||
|
re: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
kk: []string{"A", "C"},
|
||||||
|
e: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"unordered": {
|
||||||
|
re: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "B", Fields: render.Fields{"0", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "D", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
kk: []string{"C", "A"},
|
||||||
|
e: render.RowEvents{
|
||||||
|
{Row: render.Row{ID: "A", Fields: render.Fields{"1", "2", "3"}}},
|
||||||
|
{Row: render.Row{ID: "C", Fields: render.Fields{"10", "2", "3"}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var table render.TableData
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
table.RowEvents = u.re
|
||||||
|
table.Delete(u.kk)
|
||||||
|
assert.Equal(t, u.e, table.RowEvents)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,7 @@ type SelectTable struct {
|
||||||
selectedRow int
|
selectedRow int
|
||||||
selectedFn func(string) string
|
selectedFn func(string) string
|
||||||
selectionListeners []SelectedRowFunc
|
selectionListeners []SelectedRowFunc
|
||||||
marks map[string]bool
|
marks map[string]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetModel sets the table model.
|
// SetModel sets the table model.
|
||||||
|
|
@ -86,10 +86,8 @@ func (s *SelectTable) GetSelectedItems() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var items []string
|
var items []string
|
||||||
for item, marked := range s.marks {
|
for item := range s.marks {
|
||||||
if marked {
|
items = append(items, item)
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
@ -145,7 +143,7 @@ func (s *SelectTable) selectionChanged(r, c int) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.marks[s.GetSelectedItem()] {
|
if _, ok := s.marks[s.GetSelectedItem()]; ok {
|
||||||
s.SetSelectedStyle(tcell.ColorBlack, tcell.ColorCadetBlue, tcell.AttrBold)
|
s.SetSelectedStyle(tcell.ColorBlack, tcell.ColorCadetBlue, tcell.AttrBold)
|
||||||
} else {
|
} else {
|
||||||
cell := s.GetCell(r, c)
|
cell := s.GetCell(r, c)
|
||||||
|
|
@ -171,10 +169,15 @@ func (s *SelectTable) DeleteMark(k string) {
|
||||||
|
|
||||||
// ToggleMark toggles marked row
|
// ToggleMark toggles marked row
|
||||||
func (s *SelectTable) ToggleMark() {
|
func (s *SelectTable) ToggleMark() {
|
||||||
s.marks[s.GetSelectedItem()] = !s.marks[s.GetSelectedItem()]
|
sel := s.GetSelectedItem()
|
||||||
if !s.marks[s.GetSelectedItem()] {
|
if sel == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if _, ok := s.marks[sel]; ok {
|
||||||
|
delete(s.marks, s.GetSelectedItem())
|
||||||
|
} else {
|
||||||
|
s.marks[sel] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
cell := s.GetCell(s.GetSelectedRowIndex(), 0)
|
cell := s.GetCell(s.GetSelectedRowIndex(), 0)
|
||||||
s.SetSelectedStyle(
|
s.SetSelectedStyle(
|
||||||
|
|
@ -186,7 +189,8 @@ func (s *SelectTable) ToggleMark() {
|
||||||
|
|
||||||
// IsMarked returns true if this item was marked.
|
// IsMarked returns true if this item was marked.
|
||||||
func (s *Table) IsMarked(item string) bool {
|
func (s *Table) IsMarked(item string) bool {
|
||||||
return s.marks[item]
|
_, ok := s.marks[item]
|
||||||
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddSelectedRowListener add a new selected row listener.
|
// AddSelectedRowListener add a new selected row listener.
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func NewTable(gvr string) *Table {
|
||||||
SelectTable: &SelectTable{
|
SelectTable: &SelectTable{
|
||||||
Table: tview.NewTable(),
|
Table: tview.NewTable(),
|
||||||
model: model.NewTable(gvr),
|
model: model.NewTable(gvr),
|
||||||
marks: make(map[string]bool),
|
marks: make(map[string]struct{}),
|
||||||
},
|
},
|
||||||
actions: make(KeyActions),
|
actions: make(KeyActions),
|
||||||
cmdBuff: NewCmdBuff('/', FilterBuff),
|
cmdBuff: NewCmdBuff('/', FilterBuff),
|
||||||
|
|
@ -277,7 +277,7 @@ func (t *Table) buildRow(ns string, r int, re render.RowEvent, header render.Hea
|
||||||
|
|
||||||
// ClearMarks clear out marked items.
|
// ClearMarks clear out marked items.
|
||||||
func (t *Table) ClearMarks() {
|
func (t *Table) ClearMarks() {
|
||||||
t.marks = map[string]bool{}
|
t.SelectTable.ClearMarks()
|
||||||
t.Refresh()
|
t.Refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -357,12 +357,12 @@ func (b *Browser) refreshActions() {
|
||||||
if client.Can(b.meta.Verbs, "delete") {
|
if client.Can(b.meta.Verbs, "delete") {
|
||||||
aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", b.deleteCmd, true)
|
aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", b.deleteCmd, true)
|
||||||
}
|
}
|
||||||
if client.Can(b.meta.Verbs, "view") {
|
|
||||||
|
if !dao.IsK9sMeta(b.meta) {
|
||||||
aa[ui.KeyY] = ui.NewKeyAction("YAML", b.viewCmd, true)
|
aa[ui.KeyY] = ui.NewKeyAction("YAML", b.viewCmd, true)
|
||||||
}
|
|
||||||
if client.Can(b.meta.Verbs, "describe") {
|
|
||||||
aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true)
|
aa[ui.KeyD] = ui.NewKeyAction("Describe", b.describeCmd, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginActions(b, aa)
|
pluginActions(b, aa)
|
||||||
hotKeyActions(b, aa)
|
hotKeyActions(b, aa)
|
||||||
b.Actions().Add(aa)
|
b.Actions().Add(aa)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,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, 9, len(v.Hints()))
|
assert.Equal(t, 10, len(v.Hints()))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, 10, len(v.Hints()))
|
assert.Equal(t, 11, len(v.Hints()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
|
|
@ -25,6 +24,8 @@ const (
|
||||||
// Help presents a help viewer.
|
// Help presents a help viewer.
|
||||||
type Help struct {
|
type Help struct {
|
||||||
*Table
|
*Table
|
||||||
|
|
||||||
|
maxKey, maxDesc, maxRows int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHelp returns a new help viewer.
|
// NewHelp returns a new help viewer.
|
||||||
|
|
@ -44,7 +45,7 @@ func (v *Help) Init(ctx context.Context) error {
|
||||||
v.SetBorder(true)
|
v.SetBorder(true)
|
||||||
v.SetBorderPadding(0, 0, 1, 1)
|
v.SetBorderPadding(0, 0, 1, 1)
|
||||||
v.bindKeys()
|
v.bindKeys()
|
||||||
v.build(v.app.Content.Top().Hints())
|
v.build()
|
||||||
v.SetBackgroundColor(v.App().Styles.BgColor())
|
v.SetBackgroundColor(v.App().Styles.BgColor())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -59,6 +60,40 @@ func (v *Help) bindKeys() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Help) computeMaxes(hh model.MenuHints) {
|
||||||
|
v.maxKey, v.maxDesc = 0, 0
|
||||||
|
for _, h := range hh {
|
||||||
|
if len(h.Mnemonic) > v.maxKey {
|
||||||
|
v.maxKey = len(h.Mnemonic)
|
||||||
|
}
|
||||||
|
if len(h.Description) > v.maxDesc {
|
||||||
|
v.maxDesc = len(h.Description)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.maxKey += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelpFunc func() model.MenuHints
|
||||||
|
|
||||||
|
func (v *Help) build() {
|
||||||
|
v.Clear()
|
||||||
|
|
||||||
|
ff := []HelpFunc{v.app.Content.Top().Hints, v.showGeneral, v.showNav, v.showHelp}
|
||||||
|
var col int
|
||||||
|
for i, section := range []string{"RESOURCE", "GENERAL", "NAVIGATION", "HELP"} {
|
||||||
|
hh := ff[i]()
|
||||||
|
sort.Sort(hh)
|
||||||
|
v.computeMaxes(hh)
|
||||||
|
v.addSection(col, section, hh)
|
||||||
|
col += 2
|
||||||
|
}
|
||||||
|
|
||||||
|
if h, err := v.showHotKeys(); err == nil {
|
||||||
|
v.computeMaxes(h)
|
||||||
|
v.addSection(col, "HOTKEYS", h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Help) showHelp() model.MenuHints {
|
func (v *Help) showHelp() model.MenuHints {
|
||||||
return model.MenuHints{
|
return model.MenuHints{
|
||||||
{
|
{
|
||||||
|
|
@ -186,59 +221,55 @@ func (v *Help) resetTitle() {
|
||||||
v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Help) build(hh model.MenuHints) {
|
|
||||||
v.Clear()
|
|
||||||
sort.Sort(hh)
|
|
||||||
|
|
||||||
var col int
|
|
||||||
v.addSection(col, "RESOURCE", hh)
|
|
||||||
col += 2
|
|
||||||
v.addSection(col, "GENERAL", v.showGeneral())
|
|
||||||
col += 2
|
|
||||||
v.addSection(col, "NAVIGATION", v.showNav())
|
|
||||||
col += 2
|
|
||||||
if h, err := v.showHotKeys(); err == nil {
|
|
||||||
v.addSection(col, "HOTKEYS", h)
|
|
||||||
col += 2
|
|
||||||
}
|
|
||||||
v.addSection(col, "HELP", v.showHelp())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *Help) addSpacer(c int) {
|
func (v *Help) addSpacer(c int) {
|
||||||
cell := tview.NewTableCell("")
|
cell := tview.NewTableCell(render.Pad("", v.maxKey))
|
||||||
cell.SetBackgroundColor(v.App().Styles.BgColor())
|
cell.SetBackgroundColor(v.App().Styles.BgColor())
|
||||||
cell.SetExpansion(1)
|
cell.SetExpansion(1)
|
||||||
v.SetCell(0, c, cell)
|
v.SetCell(0, c, cell)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Help) addSection(c int, title string, hh model.MenuHints) {
|
func (v *Help) addSection(c int, title string, hh model.MenuHints) {
|
||||||
|
if len(hh) > v.maxRows {
|
||||||
|
v.maxRows = len(hh)
|
||||||
|
}
|
||||||
row := 0
|
row := 0
|
||||||
v.addSpacer(c)
|
|
||||||
cell := tview.NewTableCell(title)
|
cell := tview.NewTableCell(title)
|
||||||
cell.SetTextColor(tcell.ColorGreen)
|
cell.SetTextColor(tcell.ColorGreen)
|
||||||
cell.SetAttributes(tcell.AttrBold)
|
cell.SetAttributes(tcell.AttrBold)
|
||||||
cell.SetExpansion(1)
|
cell.SetExpansion(1)
|
||||||
cell.SetAlign(tview.AlignLeft)
|
cell.SetAlign(tview.AlignLeft)
|
||||||
v.SetCell(row, c+1, cell)
|
v.SetCell(row, c, cell)
|
||||||
|
v.addSpacer(c + 1)
|
||||||
row++
|
row++
|
||||||
|
|
||||||
for _, h := range hh {
|
for _, h := range hh {
|
||||||
col := c
|
col := c
|
||||||
cell := tview.NewTableCell(toMnemonic(h.Mnemonic))
|
cell := tview.NewTableCell(render.Pad(toMnemonic(h.Mnemonic), v.maxKey))
|
||||||
if _, err := strconv.Atoi(h.Mnemonic); err != nil {
|
if _, err := strconv.Atoi(h.Mnemonic); err != nil {
|
||||||
cell.SetTextColor(tcell.ColorDodgerBlue)
|
cell.SetTextColor(tcell.ColorDodgerBlue)
|
||||||
} else {
|
} else {
|
||||||
cell.SetTextColor(tcell.ColorFuchsia)
|
cell.SetTextColor(tcell.ColorFuchsia)
|
||||||
}
|
}
|
||||||
cell.SetAttributes(tcell.AttrBold)
|
cell.SetAttributes(tcell.AttrBold)
|
||||||
cell.SetAlign(tview.AlignRight)
|
|
||||||
v.SetCell(row, col, cell)
|
v.SetCell(row, col, cell)
|
||||||
col++
|
col++
|
||||||
cell = tview.NewTableCell(h.Description)
|
cell = tview.NewTableCell(render.Pad(h.Description, v.maxDesc))
|
||||||
cell.SetTextColor(tcell.ColorWhite)
|
cell.SetTextColor(tcell.ColorWhite)
|
||||||
v.SetCell(row, col, cell)
|
v.SetCell(row, col, cell)
|
||||||
row++
|
row++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(hh) < v.maxRows {
|
||||||
|
for i := v.maxRows - len(hh); i > 0; i-- {
|
||||||
|
col := c
|
||||||
|
cell := tview.NewTableCell(render.Pad("", v.maxKey))
|
||||||
|
v.SetCell(row, col, cell)
|
||||||
|
col++
|
||||||
|
cell = tview.NewTableCell(render.Pad("", v.maxDesc))
|
||||||
|
v.SetCell(row, col, cell)
|
||||||
|
row++
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMnemonic(s string) string {
|
func toMnemonic(s string) string {
|
||||||
|
|
@ -260,44 +291,3 @@ func keyConv(s string) string {
|
||||||
|
|
||||||
return strings.Replace(s, "alt", "opt", 1)
|
return strings.Replace(s, "alt", "opt", 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv {
|
|
||||||
ns, n := client.Namespaced(sel)
|
|
||||||
ctx, err := app.Conn().Config().CurrentContextName()
|
|
||||||
if err != nil {
|
|
||||||
ctx = render.NAValue
|
|
||||||
}
|
|
||||||
cluster, err := app.Conn().Config().CurrentClusterName()
|
|
||||||
if err != nil {
|
|
||||||
cluster = render.NAValue
|
|
||||||
}
|
|
||||||
user, err := app.Conn().Config().CurrentUserName()
|
|
||||||
if err != nil {
|
|
||||||
user = render.NAValue
|
|
||||||
}
|
|
||||||
groups, err := app.Conn().Config().CurrentGroupNames()
|
|
||||||
if err != nil {
|
|
||||||
groups = []string{render.NAValue}
|
|
||||||
}
|
|
||||||
var cfg string
|
|
||||||
kcfg := app.Conn().Config().Flags().KubeConfig
|
|
||||||
if kcfg != nil && *kcfg != "" {
|
|
||||||
cfg = *kcfg
|
|
||||||
}
|
|
||||||
|
|
||||||
env := K9sEnv{
|
|
||||||
"NAMESPACE": ns,
|
|
||||||
"NAME": n,
|
|
||||||
"CONTEXT": ctx,
|
|
||||||
"CLUSTER": cluster,
|
|
||||||
"USER": user,
|
|
||||||
"GROUPS": strings.Join(groups, ","),
|
|
||||||
"KUBECONFIG": cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, r := range row.Fields {
|
|
||||||
env["COL"+strconv.Itoa(i)] = r
|
|
||||||
}
|
|
||||||
|
|
||||||
return env
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package view_test
|
package view_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
|
|
@ -22,6 +23,6 @@ func TestHelp(t *testing.T) {
|
||||||
assert.Nil(t, v.Init(ctx))
|
assert.Nil(t, v.Init(ctx))
|
||||||
assert.Equal(t, 17, v.GetRowCount())
|
assert.Equal(t, 17, v.GetRowCount())
|
||||||
assert.Equal(t, 8, v.GetColumnCount())
|
assert.Equal(t, 8, v.GetColumnCount())
|
||||||
assert.Equal(t, "<ctrl-k>", v.GetCell(1, 0).Text)
|
assert.Equal(t, "<ctrl-k>", strings.TrimSpace(v.GetCell(1, 0).Text))
|
||||||
assert.Equal(t, "Kill", v.GetCell(1, 1).Text)
|
assert.Equal(t, "Kill", strings.TrimSpace(v.GetCell(1, 1).Text))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
|
|
@ -18,6 +19,47 @@ import (
|
||||||
"k8s.io/cli-runtime/pkg/printers"
|
"k8s.io/cli-runtime/pkg/printers"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv {
|
||||||
|
ns, n := client.Namespaced(sel)
|
||||||
|
ctx, err := app.Conn().Config().CurrentContextName()
|
||||||
|
if err != nil {
|
||||||
|
ctx = render.NAValue
|
||||||
|
}
|
||||||
|
cluster, err := app.Conn().Config().CurrentClusterName()
|
||||||
|
if err != nil {
|
||||||
|
cluster = render.NAValue
|
||||||
|
}
|
||||||
|
user, err := app.Conn().Config().CurrentUserName()
|
||||||
|
if err != nil {
|
||||||
|
user = render.NAValue
|
||||||
|
}
|
||||||
|
groups, err := app.Conn().Config().CurrentGroupNames()
|
||||||
|
if err != nil {
|
||||||
|
groups = []string{render.NAValue}
|
||||||
|
}
|
||||||
|
var cfg string
|
||||||
|
kcfg := app.Conn().Config().Flags().KubeConfig
|
||||||
|
if kcfg != nil && *kcfg != "" {
|
||||||
|
cfg = *kcfg
|
||||||
|
}
|
||||||
|
|
||||||
|
env := K9sEnv{
|
||||||
|
"NAMESPACE": ns,
|
||||||
|
"NAME": n,
|
||||||
|
"CONTEXT": ctx,
|
||||||
|
"CLUSTER": cluster,
|
||||||
|
"USER": user,
|
||||||
|
"GROUPS": strings.Join(groups, ","),
|
||||||
|
"KUBECONFIG": cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, r := range row.Fields {
|
||||||
|
env["COL"+strconv.Itoa(i)] = r
|
||||||
|
}
|
||||||
|
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
func describeResource(app *App, _, gvr, path string) {
|
func describeResource(app *App, _, gvr, path string) {
|
||||||
ns, n := client.Namespaced(path)
|
ns, n := client.Namespaced(path)
|
||||||
yaml, err := dao.Describe(app.Conn(), client.GVR(gvr), ns, n)
|
yaml, err := dao.Describe(app.Conn(), client.GVR(gvr), ns, n)
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,14 @@ 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.SetBindKeysFn(r.bindKeys)
|
r.bindKeys(v.Actions())
|
||||||
|
|
||||||
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) {
|
||||||
r.Actions().Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,5 +13,5 @@ func TestStatefulSetNew(t *testing.T) {
|
||||||
|
|
||||||
assert.Nil(t, s.Init(makeCtx()))
|
assert.Nil(t, s.Init(makeCtx()))
|
||||||
assert.Equal(t, "StatefulSets", s.Name())
|
assert.Equal(t, "StatefulSets", s.Name())
|
||||||
assert.Equal(t, 7, len(s.Hints()))
|
assert.Equal(t, 8, len(s.Hints()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue