address scroll jitter
parent
6001b004d1
commit
c4beaf9207
|
|
@ -72,7 +72,7 @@ func (r *ClusterRole) Fields(ns string) Row {
|
|||
i := r.instance
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, RBACPad),
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
i.Name,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/rbac/v1"
|
||||
|
|
@ -63,15 +65,54 @@ func (r *ClusterRoleBinding) Marshal(path string) (string, error) {
|
|||
|
||||
// Header return resource header.
|
||||
func (*ClusterRoleBinding) Header(_ string) Row {
|
||||
return append(Row{}, "NAME", "AGE")
|
||||
return append(Row{}, "NAME", "ROLE", "KIND", "SUBJECTS", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ClusterRoleBinding) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
|
||||
i := r.instance
|
||||
kind, ss := renderSubjects(i.Subjects)
|
||||
|
||||
return append(ff,
|
||||
r.instance.Name,
|
||||
toAge(r.instance.ObjectMeta.CreationTimestamp),
|
||||
i.Name,
|
||||
i.RoleRef.Name,
|
||||
kind,
|
||||
ss,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func renderSubjects(ss []v1.Subject) (kind string, subjects string) {
|
||||
if len(ss) == 0 {
|
||||
return NAValue, ""
|
||||
}
|
||||
|
||||
var tt []string
|
||||
for _, s := range ss {
|
||||
kind = toSubjectAlias(s.Kind)
|
||||
tt = append(tt, s.Name)
|
||||
}
|
||||
return kind, strings.Join(tt, ",")
|
||||
}
|
||||
|
||||
func toSubjectAlias(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
}
|
||||
|
||||
switch s {
|
||||
case v1.UserKind:
|
||||
return "USR"
|
||||
case v1.GroupKind:
|
||||
return "GRP"
|
||||
case v1.ServiceAccountKind:
|
||||
return "SA"
|
||||
default:
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func TestCRBListData(t *testing.T) {
|
|||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, resource.NotNamespaced, l.GetNamespace())
|
||||
row := td.Rows["fred"]
|
||||
assert.Equal(t, 2, len(row.Deltas))
|
||||
assert.Equal(t, 5, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ func TestCRListAccess(t *testing.T) {
|
|||
|
||||
func TestCRFields(t *testing.T) {
|
||||
r := newClusterRole().Fields("blee")
|
||||
assert.Equal(t, resource.Pad("fred", resource.RBACPad), r[0])
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCRFieldsAllNS(t *testing.T) {
|
||||
r := newClusterRole().Fields(resource.AllNamespaces)
|
||||
assert.Equal(t, resource.Pad("fred", resource.RBACPad), r[0])
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCRMarshal(t *testing.T) {
|
||||
|
|
@ -84,7 +84,7 @@ func TestCRListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", resource.RBACPad)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func (r *CronJob) Fields(ns string) Row {
|
|||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, Pad(i.Namespace, NSPad))
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
lastScheduled := "<none>"
|
||||
|
|
@ -111,11 +111,11 @@ func (r *CronJob) Fields(ns string) Row {
|
|||
}
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, NamePad),
|
||||
i.Name,
|
||||
i.Spec.Schedule,
|
||||
boolPtrToStr(i.Spec.Suspend),
|
||||
strconv.Itoa(len(i.Status.Active)),
|
||||
lastScheduled,
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ func TestCronJobListAccess(t *testing.T) {
|
|||
|
||||
func TestCronJobFields(t *testing.T) {
|
||||
r := newCronJob().Fields("blee")
|
||||
assert.Equal(t, resource.Pad("fred", resource.NamePad), r[0])
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestCronJobMarshal(t *testing.T) {
|
||||
|
|
@ -75,7 +75,7 @@ func TestCronJobListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -36,14 +36,6 @@ const (
|
|||
NAValue = "<n/a>"
|
||||
)
|
||||
|
||||
// Columns Padding...
|
||||
const (
|
||||
NSPad = 13
|
||||
NamePad = 50
|
||||
AgePad = 5
|
||||
RBACPad = 80
|
||||
)
|
||||
|
||||
func asPerc(f float64) string {
|
||||
return fmt.Sprintf("%d%%", int(f))
|
||||
}
|
||||
|
|
@ -98,21 +90,6 @@ func toAge(timestamp metav1.Time) string {
|
|||
return duration.HumanDuration(time.Since(timestamp.Time))
|
||||
}
|
||||
|
||||
// FixCol set column width to specified size by either truncating or padding.
|
||||
func FixCol(s string, size int) string {
|
||||
if len(s) > size {
|
||||
return Truncate(s, size)
|
||||
}
|
||||
return s + strings.Repeat(" ", size-len(s))
|
||||
}
|
||||
|
||||
// Pad a string up to the given length.
|
||||
func Pad(s string, l int) string {
|
||||
fmat := "%-" + strconv.Itoa(l) + "s"
|
||||
|
||||
return fmt.Sprintf(fmat, s)
|
||||
}
|
||||
|
||||
// Truncate a string to the given l and suffix ellipsis if needed.
|
||||
func Truncate(str string, width int) string {
|
||||
return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis))
|
||||
|
|
|
|||
|
|
@ -77,22 +77,6 @@ func TestNa(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPad(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
l int
|
||||
e string
|
||||
}{
|
||||
{"fred", 10, "fred "},
|
||||
{"fred", 6, "fred "},
|
||||
{"fred", 4, "fred"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, Pad(u.s, u.l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTruncate(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
|
|
@ -109,22 +93,6 @@ func TestTruncate(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSizeCol(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
l int
|
||||
e string
|
||||
}{
|
||||
{"fred", 3, "fr…"},
|
||||
{"01234567890", 10, "012345678…"},
|
||||
{"fred", 10, "fred "},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, FixCol(u.s, u.l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapToStr(t *testing.T) {
|
||||
uu := []struct {
|
||||
i map[string]string
|
||||
|
|
|
|||
|
|
@ -132,18 +132,18 @@ func (r *Job) Fields(ns string) Row {
|
|||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, Pad(i.Namespace, NSPad))
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
cc, ii := r.toContainers(i.Spec.Template.Spec)
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, NamePad),
|
||||
i.Name,
|
||||
r.toCompletion(i.Spec, i.Status),
|
||||
r.toDuration(i.Status),
|
||||
cc,
|
||||
ii,
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ func TestJobListAccess(t *testing.T) {
|
|||
|
||||
func TestJobFields(t *testing.T) {
|
||||
r := newJob().Fields("blee")
|
||||
assert.Equal(t, resource.Pad("fred", resource.NamePad), r[0])
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestJobMarshal(t *testing.T) {
|
||||
|
|
@ -74,7 +74,7 @@ func TestJobListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -203,14 +203,14 @@ func (r *Pod) Fields(ns string) Row {
|
|||
i := r.instance
|
||||
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, FixCol(i.Namespace, NSPad))
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
ss := i.Status.ContainerStatuses
|
||||
cr, _, rc := r.statuses(ss)
|
||||
|
||||
return append(ff,
|
||||
FixCol(i.ObjectMeta.Name, NamePad),
|
||||
i.ObjectMeta.Name,
|
||||
strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)),
|
||||
r.phase(i.Status),
|
||||
strconv.Itoa(rc),
|
||||
|
|
@ -219,7 +219,7 @@ func (r *Pod) Fields(ns string) Row {
|
|||
i.Status.PodIP,
|
||||
i.Spec.NodeName,
|
||||
r.mapQOS(i.Status.QOSClass),
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -229,13 +229,14 @@ func (r *Pod) Fields(ns string) Row {
|
|||
func (*Pod) mapQOS(class v1.PodQOSClass) string {
|
||||
switch class {
|
||||
case v1.PodQOSGuaranteed:
|
||||
return "Ga"
|
||||
return "GA"
|
||||
case v1.PodQOSBurstable:
|
||||
return "Bu"
|
||||
return "BU"
|
||||
default:
|
||||
return "BE"
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
|
||||
for _, c := range ss {
|
||||
if c.State.Terminated != nil {
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ func TestPodListAccess(t *testing.T) {
|
|||
|
||||
func TestPodFields(t *testing.T) {
|
||||
r := newPod().Fields("blee")
|
||||
assert.Equal(t, resource.FixCol("fred", 50), r[0])
|
||||
assert.Equal(t, "fred", r[0])
|
||||
}
|
||||
|
||||
func TestPodMarshal(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -79,12 +79,12 @@ func (r *Role) Fields(ns string) Row {
|
|||
|
||||
i := r.instance
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, Pad(i.Namespace, NSPad))
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, RBACPad),
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
i.Name,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/rbac/v1"
|
||||
|
|
@ -70,49 +68,25 @@ func (*RoleBinding) Header(ns string) Row {
|
|||
hh = append(hh, "NAMESPACE")
|
||||
}
|
||||
|
||||
return append(hh, "NAME", "ROLE", "SUBJECTS", "AGE")
|
||||
return append(hh, "NAME", "ROLE", "KIND", "SUBJECTS", "AGE")
|
||||
}
|
||||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *RoleBinding) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
i := r.instance
|
||||
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
kind, ss := renderSubjects(i.Subjects)
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
i.RoleRef.Name,
|
||||
r.toSubjects(i.Subjects),
|
||||
kind,
|
||||
ss,
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func (r *RoleBinding) toSubjects(ss []v1.Subject) string {
|
||||
var acc string
|
||||
for i, s := range ss {
|
||||
acc += s.Name + "/" + r.toSubjectAlias(s.Kind)
|
||||
if i < len(ss)-1 {
|
||||
acc += ","
|
||||
}
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
func (r *RoleBinding) toSubjectAlias(s string) string {
|
||||
switch s {
|
||||
case v1.UserKind:
|
||||
return "USR"
|
||||
case v1.GroupKind:
|
||||
return "GRP"
|
||||
case v1.ServiceAccountKind:
|
||||
return "SA"
|
||||
default:
|
||||
return strings.ToUpper(s)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import (
|
|||
)
|
||||
|
||||
func TestToSubjectAlias(t *testing.T) {
|
||||
r := RoleBinding{}
|
||||
|
||||
uu := []struct {
|
||||
i string
|
||||
e string
|
||||
|
|
@ -20,38 +18,44 @@ func TestToSubjectAlias(t *testing.T) {
|
|||
{"fred", "FRED"},
|
||||
}
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, r.toSubjectAlias(u.i))
|
||||
assert.Equal(t, u.e, toSubjectAlias(u.i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSubjects(t *testing.T) {
|
||||
r := RoleBinding{}
|
||||
|
||||
func TestRenderSubjects(t *testing.T) {
|
||||
uu := []struct {
|
||||
i []rbacv1.Subject
|
||||
e string
|
||||
ss []rbacv1.Subject
|
||||
ek string
|
||||
e string
|
||||
}{
|
||||
{
|
||||
[]rbacv1.Subject{
|
||||
{Name: "blee", Kind: rbacv1.UserKind},
|
||||
},
|
||||
"blee/USR",
|
||||
"USR",
|
||||
"blee",
|
||||
},
|
||||
{
|
||||
[]rbacv1.Subject{},
|
||||
"<n/a>",
|
||||
"",
|
||||
},
|
||||
}
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, r.toSubjects(u.i))
|
||||
kind, ss := renderSubjects(u.ss)
|
||||
assert.Equal(t, u.e, ss)
|
||||
assert.Equal(t, u.ek, kind)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkToSubjects(b *testing.B) {
|
||||
var r RoleBinding
|
||||
ss := []rbacv1.Subject{
|
||||
{Name: "blee", Kind: rbacv1.UserKind},
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.toSubjects(ss)
|
||||
renderSubjects(ss)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func TestRBListData(t *testing.T) {
|
|||
assert.Equal(t, 1, len(td.Rows))
|
||||
assert.Equal(t, "blee", l.GetNamespace())
|
||||
row := td.Rows["blee/fred"]
|
||||
assert.Equal(t, 4, len(row.Deltas))
|
||||
assert.Equal(t, 5, len(row.Deltas))
|
||||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestRoleListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", resource.RBACPad)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -75,18 +75,18 @@ func (*ReplicaSet) Header(ns string) Row {
|
|||
|
||||
// Fields retrieves displayable fields.
|
||||
func (r *ReplicaSet) Fields(ns string) Row {
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, Pad(r.instance.Namespace, NSPad))
|
||||
}
|
||||
|
||||
i := r.instance
|
||||
|
||||
ff := make(Row, 0, len(r.Header(ns)))
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
Pad(i.Name, NamePad),
|
||||
i.Name,
|
||||
strconv.Itoa(int(*i.Spec.Replicas)),
|
||||
strconv.Itoa(int(i.Status.Replicas)),
|
||||
strconv.Itoa(int(i.Status.ReadyReplicas)),
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func TestReplicaSetListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -91,16 +91,16 @@ func (r *Service) Fields(ns string) Row {
|
|||
i := r.instance
|
||||
|
||||
if ns == AllNamespaces {
|
||||
ff = append(ff, Pad(i.Namespace, NSPad))
|
||||
ff = append(ff, i.Namespace)
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
Pad(i.ObjectMeta.Name, NamePad),
|
||||
i.ObjectMeta.Name,
|
||||
string(i.Spec.Type),
|
||||
i.Spec.ClusterIP,
|
||||
r.toIPs(i.Spec.Type, r.getSvcExtIPS(i)),
|
||||
r.toPorts(i.Spec.Ports),
|
||||
Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad),
|
||||
toAge(i.ObjectMeta.CreationTimestamp),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ func TestSvcFields(t *testing.T) {
|
|||
{
|
||||
i: newSvc(),
|
||||
e: resource.Row{
|
||||
resource.Pad("blee", resource.NSPad),
|
||||
resource.Pad("fred", resource.NamePad),
|
||||
"blee",
|
||||
"fred",
|
||||
"ClusterIP",
|
||||
"1.1.1.1",
|
||||
"2.2.2.2",
|
||||
|
|
@ -102,7 +102,7 @@ func TestSVCListData(t *testing.T) {
|
|||
for _, d := range row.Deltas {
|
||||
assert.Equal(t, "", d)
|
||||
}
|
||||
assert.Equal(t, resource.Row{resource.Pad("fred", 50)}, row.Fields[:1])
|
||||
assert.Equal(t, resource.Row{"fred"}, row.Fields[:1])
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
|
|
|||
|
|
@ -124,9 +124,9 @@ func (v *aliasView) hydrate() resource.TableData {
|
|||
|
||||
for k := range cmds {
|
||||
fields := resource.Row{
|
||||
resource.Pad(k, 30),
|
||||
resource.Pad(cmds[k].title, 30),
|
||||
resource.Pad(cmds[k].api, 30),
|
||||
pad(k, 30),
|
||||
pad(cmds[k].title, 30),
|
||||
pad(cmds[k].api, 30),
|
||||
}
|
||||
data.Rows[k] = &resource.RowEvent{
|
||||
Action: resource.New,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,10 @@ type (
|
|||
|
||||
resourceViewer interface {
|
||||
igniter
|
||||
|
||||
setEnterFn(enterFn)
|
||||
setColorerFn(colorerFn)
|
||||
setDecorateFn(decorateFn)
|
||||
}
|
||||
|
||||
appView struct {
|
||||
|
|
|
|||
|
|
@ -69,10 +69,16 @@ func (c *command) run(cmd string) bool {
|
|||
} else {
|
||||
r = res.listFn(c.app.conn(), resource.DefaultNamespace)
|
||||
}
|
||||
v = res.viewFn(res.title, c.app, r, res.colorerFn)
|
||||
v = res.viewFn(res.title, c.app, r)
|
||||
if res.colorerFn != nil {
|
||||
v.setColorerFn(res.colorerFn)
|
||||
}
|
||||
if res.enterFn != nil {
|
||||
v.setEnterFn(res.enterFn)
|
||||
}
|
||||
if res.decorateFn != nil {
|
||||
v.setDecorateFn(res.decorateFn)
|
||||
}
|
||||
const fmat = "Viewing %s in namespace %s..."
|
||||
c.app.flash(flashInfo, fmt.Sprintf(fmat, res.title, c.app.config.ActiveNamespace()))
|
||||
log.Debug().Msgf("Running command %s", cmd)
|
||||
|
|
@ -87,16 +93,16 @@ func (c *command) run(cmd string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
n := res.Plural
|
||||
if len(n) == 0 {
|
||||
n = res.Singular
|
||||
name := res.Plural
|
||||
if len(name) == 0 {
|
||||
name = res.Singular
|
||||
}
|
||||
v = newResourceView(
|
||||
res.Kind,
|
||||
c.app,
|
||||
resource.NewCustomList(c.app.conn(), "", res.Group, res.Version, n),
|
||||
defaultColorer,
|
||||
resource.NewCustomList(c.app.conn(), "", res.Group, res.Version, name),
|
||||
)
|
||||
v.setColorerFn(defaultColorer)
|
||||
c.exec(cmd, v)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ type contextView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newContextView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
v := contextView{newResourceView(t, app, list, c).(*resourceView)}
|
||||
func newContextView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := contextView{newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
|
|
|
|||
|
|
@ -11,12 +11,13 @@ type cronJobView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newCronJobView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
func newCronJobView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := cronJobView{
|
||||
resourceView: newResourceView(t, app, list, c).(*resourceView),
|
||||
resourceView: newResourceView(t, app, list).(*resourceView),
|
||||
}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.switchPage("cronjob")
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +31,7 @@ func (v *cronJobView) trigger(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.app.flash(flashErr, "Boom!", err.Error())
|
||||
return evt
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ func (v *fuView) prepRow(ns, res, grp, binding string, verbs []string) resource.
|
|||
grp = toGroup(grp)
|
||||
}
|
||||
|
||||
return v.makeRow(resource.Pad(ns, nsLen), resource.Pad(res, nameLen), resource.Pad(grp, groupLen), binding, asVerbs(verbs...))
|
||||
return v.makeRow(ns, res, grp, binding, asVerbs(verbs...))
|
||||
}
|
||||
|
||||
func (*fuView) makeRow(ns, res, group, binding string, verbs []string) resource.Row {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ func toPerc(f float64) string {
|
|||
}
|
||||
|
||||
func deltas(c, n string) string {
|
||||
c, n = strings.TrimSpace(c), strings.TrimSpace(n)
|
||||
|
||||
// log.Debug().Msgf("`%s` vs `%s`", c, n)
|
||||
|
||||
if c == "n/a" {
|
||||
return n
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ type jobView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newJobView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
v := jobView{newResourceView(t, app, list, c).(*resourceView)}
|
||||
func newJobView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := jobView{newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.AddPage("logs", newLogsView(&v), true, false)
|
||||
v.switchPage("job")
|
||||
}
|
||||
v.AddPage("logs", newLogsView(&v), true, false)
|
||||
v.switchPage("job")
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
|
|
@ -60,6 +61,7 @@ func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
v.switchPage("logs")
|
||||
l.init()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,13 +23,16 @@ type namespaceView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newNamespaceView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
v := namespaceView{newResourceView(t, app, list, c).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.selectedFn = v.getSelectedItem
|
||||
v.decorateDataFn = v.decorate
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
v.switchPage("ns")
|
||||
func newNamespaceView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := namespaceView{newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.selectedFn = v.getSelectedItem
|
||||
v.decorateFn = v.decorate
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
v.switchPage("ns")
|
||||
}
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +47,7 @@ func (v *namespaceView) switchNsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
v.useNamespace(v.getSelectedItem())
|
||||
v.app.gotoResource("po", true)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +56,7 @@ func (v *namespaceView) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
v.useNamespace(v.getSelectedItem())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -92,5 +97,6 @@ func (v *namespaceView) decorate(data resource.TableData) resource.TableData {
|
|||
r.Action = resource.Unchanged
|
||||
}
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ type nodeView struct {
|
|||
*resourceView
|
||||
}
|
||||
|
||||
func newNodeView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
v := nodeView{newResourceView(t, app, list, c).(*resourceView)}
|
||||
func newNodeView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := nodeView{newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.switchPage("no")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
)
|
||||
|
||||
type maxyPad []int
|
||||
|
||||
func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
|
||||
for index, h := range table.Header {
|
||||
pads[index] = len(h)
|
||||
if index == sortCol {
|
||||
pads[index] = len(h) + 2
|
||||
}
|
||||
}
|
||||
|
||||
var row int
|
||||
for _, rev := range table.Rows {
|
||||
for index, field := range rev.Fields {
|
||||
if len(field) > pads[index] && isASCII(field) {
|
||||
pads[index] = len([]rune(field))
|
||||
}
|
||||
}
|
||||
row++
|
||||
}
|
||||
}
|
||||
|
||||
func isASCII(s string) bool {
|
||||
for i := 0; i < len(s); i++ {
|
||||
if s[i] > unicode.MaxASCII {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Pad a string up to the given length or truncates if greater than length.
|
||||
func pad(s string, width int) string {
|
||||
if len(s) == width {
|
||||
return s
|
||||
}
|
||||
|
||||
if len(s) > width {
|
||||
return resource.Truncate(s, width)
|
||||
}
|
||||
|
||||
return s + strings.Repeat(" ", width-len(s))
|
||||
}
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMaxColumn(t *testing.T) {
|
||||
uu := []struct {
|
||||
t resource.TableData
|
||||
s int
|
||||
e maxyPad
|
||||
}{
|
||||
{
|
||||
resource.TableData{
|
||||
Header: resource.Row{"A", "B"},
|
||||
Rows: resource.RowEvents{
|
||||
"r1": &resource.RowEvent{Fields: resource.Row{"hello", "world"}},
|
||||
"r2": &resource.RowEvent{Fields: resource.Row{"yo", "mama"}},
|
||||
},
|
||||
},
|
||||
0,
|
||||
maxyPad{5, 5},
|
||||
},
|
||||
{
|
||||
resource.TableData{
|
||||
Header: resource.Row{"A", "B"},
|
||||
Rows: resource.RowEvents{
|
||||
"r1": &resource.RowEvent{Fields: resource.Row{"hello", "world"}},
|
||||
"r2": &resource.RowEvent{Fields: resource.Row{"yo", "mama"}},
|
||||
},
|
||||
},
|
||||
1,
|
||||
maxyPad{5, 5},
|
||||
},
|
||||
{
|
||||
resource.TableData{
|
||||
Header: resource.Row{"A", "B"},
|
||||
Rows: resource.RowEvents{
|
||||
"r1": &resource.RowEvent{Fields: resource.Row{"Hello World lord of ipsums 😅", "world"}},
|
||||
"r2": &resource.RowEvent{Fields: resource.Row{"o", "mama"}},
|
||||
},
|
||||
},
|
||||
0,
|
||||
maxyPad{3, 5},
|
||||
},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
pads := make(maxyPad, len(u.t.Header))
|
||||
computeMaxColumns(pads, u.s, u.t)
|
||||
assert.Equal(t, u.e, pads)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsASCII(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
e bool
|
||||
}{
|
||||
{"hello", true},
|
||||
{"Yo! 😄", false},
|
||||
{"😄", false},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, isASCII(u.s))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPad(t *testing.T) {
|
||||
uu := []struct {
|
||||
s string
|
||||
l int
|
||||
e string
|
||||
}{
|
||||
{"fred", 3, "fr…"},
|
||||
{"01234567890", 10, "012345678…"},
|
||||
{"fred", 10, "fred "},
|
||||
{"fred", 6, "fred "},
|
||||
{"fred", 4, "fred"},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, pad(u.s, u.l))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMaxColumn(b *testing.B) {
|
||||
table := resource.TableData{
|
||||
Header: resource.Row{"A", "B"},
|
||||
Rows: resource.RowEvents{
|
||||
"r1": &resource.RowEvent{Fields: resource.Row{"hello", "world"}},
|
||||
"r2": &resource.RowEvent{Fields: resource.Row{"yo", "mama"}},
|
||||
},
|
||||
}
|
||||
|
||||
pads := make(maxyPad, len(table.Header))
|
||||
|
||||
b.ReportAllocs()
|
||||
b.ResetTimer()
|
||||
for n := 0; n < b.N; n++ {
|
||||
computeMaxColumns(pads, 0, table)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,8 @@ type loggable interface {
|
|||
switchPage(n string)
|
||||
}
|
||||
|
||||
func newPodView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
v := podView{newResourceView(t, app, list, c).(*resourceView)}
|
||||
func newPodView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := podView{newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
}
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ func prepRow(res, grp string, verbs []string) resource.Row {
|
|||
grp = toGroup(grp)
|
||||
}
|
||||
|
||||
return makeRow(resource.Pad(res, nameLen), resource.Pad(grp, groupLen), asVerbs(verbs...))
|
||||
return makeRow(res, grp, asVerbs(verbs...))
|
||||
}
|
||||
|
||||
func makeRow(res, group string, verbs []string) resource.Row {
|
||||
|
|
@ -329,7 +329,7 @@ func asVerbs(verbs ...string) resource.Row {
|
|||
|
||||
r := make(resource.Row, 0, len(k8sVerbs)+1)
|
||||
for _, v := range k8sVerbs {
|
||||
r = append(r, resource.Pad(toVerbIcon(hasVerb(verbs, v)), 4))
|
||||
r = append(r, toVerbIcon(hasVerb(verbs, v)))
|
||||
}
|
||||
|
||||
var unknowns []string
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*.*": resource.Row{resource.Pad("*.*", 60), resource.Pad("*", 30), ok, ok, ok, ok, ok, ok, ok, ok, ""},
|
||||
"*.*": resource.Row{"*.*", "*", ok, ok, ok, ok, ok, ok, ok, ok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -67,7 +67,7 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"get"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*.*": resource.Row{resource.Pad("*.*", 60), resource.Pad("*", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"*.*": resource.Row{"*.*", "*", ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{""}, Resources: []string{"*"}, Verbs: []string{"list"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*": resource.Row{resource.Pad("*", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"*": resource.Row{"*", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -83,8 +83,8 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, ResourceNames: []string{"fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"pods": resource.Row{resource.Pad("pods", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"pods/fred": resource.Row{resource.Pad("pods/fred", 60), resource.Pad("v1", 30), nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"pods": resource.Row{"pods", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"pods/fred": resource.Row{"pods/fred", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -92,7 +92,7 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"/fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"/fred": resource.Row{resource.Pad("/fred", 60), resource.Pad("<n/a>", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"/fred": resource.Row{"/fred", "<n/a>", ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -100,7 +100,7 @@ func TestParseRules(t *testing.T) {
|
|||
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"/fred": resource.Row{resource.Pad("/fred", 60), resource.Pad("<n/a>", 30), ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"/fred": resource.Row{"/fred", "<n/a>", ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,20 +7,22 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
viewFn func(ns string, app *appView, list resource.List, colorer colorerFn) resourceViewer
|
||||
listFn func(c resource.Connection, ns string) resource.List
|
||||
listMxFn func(c resource.Connection, mx resource.MetricsServer, ns string) resource.List
|
||||
colorerFn func(ns string, evt *resource.RowEvent) tcell.Color
|
||||
enterFn func(app *appView, ns, resource, selection string)
|
||||
viewFn func(ns string, app *appView, list resource.List) resourceViewer
|
||||
listFn func(c resource.Connection, ns string) resource.List
|
||||
listMxFn func(c resource.Connection, mx resource.MetricsServer, ns string) resource.List
|
||||
colorerFn func(ns string, evt *resource.RowEvent) tcell.Color
|
||||
enterFn func(app *appView, ns, resource, selection string)
|
||||
decorateFn func(resource.TableData) resource.TableData
|
||||
|
||||
resCmd struct {
|
||||
title string
|
||||
api string
|
||||
viewFn viewFn
|
||||
listFn listFn
|
||||
listMxFn listMxFn
|
||||
enterFn enterFn
|
||||
colorerFn colorerFn
|
||||
title string
|
||||
api string
|
||||
viewFn viewFn
|
||||
listFn listFn
|
||||
listMxFn listMxFn
|
||||
enterFn enterFn
|
||||
colorerFn colorerFn
|
||||
decorateFn decorateFn
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -86,40 +88,36 @@ func showRBAC(app *appView, ns, resource, selection string) {
|
|||
func resourceViews() map[string]resCmd {
|
||||
return map[string]resCmd{
|
||||
"cm": {
|
||||
title: "ConfigMaps",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewConfigMapList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "ConfigMaps",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewConfigMapList,
|
||||
},
|
||||
"cr": {
|
||||
title: "ClusterRoles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleList,
|
||||
enterFn: showRBAC,
|
||||
colorerFn: defaultColorer,
|
||||
title: "ClusterRoles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleList,
|
||||
enterFn: showRBAC,
|
||||
},
|
||||
"crb": {
|
||||
title: "ClusterRoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleBindingList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "ClusterRoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewClusterRoleBindingList,
|
||||
// decorateFn: crbDecorator,
|
||||
},
|
||||
"crd": {
|
||||
title: "CustomResourceDefinitions",
|
||||
api: "apiextensions.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewCRDList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "CustomResourceDefinitions",
|
||||
api: "apiextensions.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewCRDList,
|
||||
},
|
||||
"cj": {
|
||||
title: "CronJobs",
|
||||
api: "batch",
|
||||
viewFn: newCronJobView,
|
||||
listFn: resource.NewCronJobList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "CronJobs",
|
||||
api: "batch",
|
||||
viewFn: newCronJobView,
|
||||
listFn: resource.NewCronJobList,
|
||||
},
|
||||
"ctx": {
|
||||
title: "Contexts",
|
||||
|
|
@ -143,11 +141,10 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: dpColorer,
|
||||
},
|
||||
"ep": {
|
||||
title: "EndPoints",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEndpointsList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "EndPoints",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewEndpointsList,
|
||||
},
|
||||
"ev": {
|
||||
title: "Events",
|
||||
|
|
@ -157,25 +154,22 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: evColorer,
|
||||
},
|
||||
"hpa": {
|
||||
title: "HorizontalPodAutoscalers",
|
||||
api: "autoscaling",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewHPAList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "HorizontalPodAutoscalers",
|
||||
api: "autoscaling",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewHPAList,
|
||||
},
|
||||
"ing": {
|
||||
title: "Ingress",
|
||||
api: "extensions",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewIngressList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "Ingress",
|
||||
api: "extensions",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewIngressList,
|
||||
},
|
||||
"jo": {
|
||||
title: "Jobs",
|
||||
api: "batch",
|
||||
viewFn: newJobView,
|
||||
listFn: resource.NewJobList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "Jobs",
|
||||
api: "batch",
|
||||
viewFn: newJobView,
|
||||
listFn: resource.NewJobList,
|
||||
},
|
||||
"no": {
|
||||
title: "Nodes",
|
||||
|
|
@ -220,11 +214,10 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: pvcColorer,
|
||||
},
|
||||
"rb": {
|
||||
title: "RoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleBindingList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "RoleBindings",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleBindingList,
|
||||
},
|
||||
"rc": {
|
||||
title: "ReplicationControllers",
|
||||
|
|
@ -234,12 +227,11 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: rsColorer,
|
||||
},
|
||||
"ro": {
|
||||
title: "Roles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleList,
|
||||
enterFn: showRBAC,
|
||||
colorerFn: defaultColorer,
|
||||
title: "Roles",
|
||||
api: "rbac.authorization.k8s.io",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewRoleList,
|
||||
enterFn: showRBAC,
|
||||
},
|
||||
"rs": {
|
||||
title: "ReplicaSets",
|
||||
|
|
@ -249,18 +241,16 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: rsColorer,
|
||||
},
|
||||
"sa": {
|
||||
title: "ServiceAccounts",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceAccountList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "ServiceAccounts",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceAccountList,
|
||||
},
|
||||
"sec": {
|
||||
title: "Secrets",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewSecretList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "Secrets",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewSecretList,
|
||||
},
|
||||
"sts": {
|
||||
title: "StatefulSets",
|
||||
|
|
@ -270,11 +260,11 @@ func resourceViews() map[string]resCmd {
|
|||
colorerFn: stsColorer,
|
||||
},
|
||||
"svc": {
|
||||
title: "Services",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceList,
|
||||
colorerFn: defaultColorer,
|
||||
title: "Services",
|
||||
api: "",
|
||||
viewFn: newResourceView,
|
||||
listFn: resource.NewServiceList,
|
||||
// decorateFn: svcDecorator,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,12 @@ type (
|
|||
enterFn enterFn
|
||||
extraActionsFn func(keyActions)
|
||||
selectedFn func() string
|
||||
decorateDataFn func(resource.TableData) resource.TableData
|
||||
decorateFn decorateFn
|
||||
colorerFn colorerFn
|
||||
}
|
||||
)
|
||||
|
||||
func newResourceView(title string, app *appView, list resource.List, c colorerFn) resourceViewer {
|
||||
func newResourceView(title string, app *appView, list resource.List) resourceViewer {
|
||||
v := resourceView{
|
||||
app: app,
|
||||
title: title,
|
||||
|
|
@ -60,7 +61,6 @@ func newResourceView(title string, app *appView, list resource.List, c colorerFn
|
|||
|
||||
tv := newTableView(app, v.title)
|
||||
{
|
||||
tv.SetColorer(c)
|
||||
tv.SetSelectionChangedFunc(v.selChanged)
|
||||
}
|
||||
v.AddPage(v.list.GetName(), tv, true, true)
|
||||
|
|
@ -80,6 +80,12 @@ func newResourceView(title string, app *appView, list resource.List, c colorerFn
|
|||
func (v *resourceView) init(ctx context.Context, ns string) {
|
||||
v.selectedItem, v.selectedNS = noSelection, ns
|
||||
|
||||
colorer := defaultColorer
|
||||
if v.colorerFn != nil {
|
||||
colorer = v.colorerFn
|
||||
}
|
||||
v.getTV().setColorer(colorer)
|
||||
|
||||
go func(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
|
|
@ -107,10 +113,6 @@ func (v *resourceView) selChanged(r, c int) {
|
|||
v.getTV().cmdBuff.setActive(false)
|
||||
}
|
||||
|
||||
func (v *resourceView) colorFn(f colorerFn) {
|
||||
v.getTV().SetColorer(f)
|
||||
}
|
||||
|
||||
func (v *resourceView) getSelectedItem() string {
|
||||
if v.selectedFn != nil {
|
||||
return v.selectedFn()
|
||||
|
|
@ -125,10 +127,19 @@ func (v *resourceView) hints() hints {
|
|||
return v.CurrentPage().Item.(hinter).hints()
|
||||
}
|
||||
|
||||
func (v *resourceView) setColorerFn(f colorerFn) {
|
||||
v.colorerFn = f
|
||||
v.getTV().setColorer(f)
|
||||
}
|
||||
|
||||
func (v *resourceView) setEnterFn(f enterFn) {
|
||||
v.enterFn = f
|
||||
}
|
||||
|
||||
func (v *resourceView) setDecorateFn(f decorateFn) {
|
||||
v.decorateFn = f
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Actions...
|
||||
|
||||
|
|
@ -275,12 +286,12 @@ func (v *resourceView) refresh() {
|
|||
v.list.SetNamespace(v.selectedNS)
|
||||
}
|
||||
if err := v.list.Reconcile(); err != nil {
|
||||
log.Warn().Msgf("Reconcile %v", err)
|
||||
log.Error().Err(err).Msg("Reconciliation failed")
|
||||
v.app.flash(flashErr, err.Error())
|
||||
}
|
||||
data := v.list.Data()
|
||||
if v.decorateDataFn != nil {
|
||||
data = v.decorateDataFn(data)
|
||||
if v.decorateFn != nil {
|
||||
data = v.decorateFn(data)
|
||||
}
|
||||
v.getTV().update(data)
|
||||
v.selectItem(v.selectedRow, 0)
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ func (v *tableView) setDeleted() {
|
|||
}
|
||||
|
||||
// SetColorer sets up table row color management.
|
||||
func (v *tableView) SetColorer(f colorerFn) {
|
||||
func (v *tableView) setColorer(f colorerFn) {
|
||||
v.colorerFn = f
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +283,7 @@ func (v *tableView) filtered() resource.TableData {
|
|||
return filtered
|
||||
}
|
||||
|
||||
func (v *tableView) displayCol(index int, name string) string {
|
||||
func (v *tableView) sortIndicator(index int, name string) string {
|
||||
if v.sortCol.index != index {
|
||||
return name
|
||||
}
|
||||
|
|
@ -292,13 +292,13 @@ func (v *tableView) displayCol(index int, name string) string {
|
|||
if v.sortCol.asc {
|
||||
order = "↑"
|
||||
}
|
||||
return fmt.Sprintf("%s[green::]%s[::]", name, order)
|
||||
return fmt.Sprintf("%s [green::]%s[::]", name, order)
|
||||
}
|
||||
|
||||
func (v *tableView) doUpdate(data resource.TableData) {
|
||||
v.currentNS = data.Namespace
|
||||
if v.currentNS == resource.AllNamespaces || v.currentNS == "*" {
|
||||
v.actions[KeyShiftS] = newKeyAction("Sort Namespace", v.sortNamespaceCmd, true)
|
||||
v.actions[KeyShiftP] = newKeyAction("Sort Namespace", v.sortNamespaceCmd, true)
|
||||
} else {
|
||||
delete(v.actions, KeyShiftS)
|
||||
}
|
||||
|
|
@ -317,9 +317,11 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
|||
v.sortCol.index = 0
|
||||
}
|
||||
|
||||
pads := make(maxyPad, len(data.Header))
|
||||
computeMaxColumns(pads, v.sortCol.index, data)
|
||||
var row int
|
||||
for col, h := range data.Header {
|
||||
v.addHeaderCell(col, h)
|
||||
v.addHeaderCell(col, h, pads)
|
||||
}
|
||||
row++
|
||||
|
||||
|
|
@ -327,62 +329,64 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
|||
if v.sortFn != nil {
|
||||
sortFn = v.sortFn
|
||||
}
|
||||
|
||||
keys := make([]string, len(data.Rows))
|
||||
v.sortRows(data.Rows, sortFn, v.sortCol, keys)
|
||||
groupKeys := map[string][]string{}
|
||||
for _, k := range keys {
|
||||
grp := data.Rows[k].Fields[v.sortCol.index]
|
||||
if s, ok := groupKeys[grp]; ok {
|
||||
s = append(s, k)
|
||||
groupKeys[grp] = s
|
||||
} else {
|
||||
groupKeys[grp] = []string{k}
|
||||
}
|
||||
}
|
||||
|
||||
// Performs secondary to sort by name for each groups.
|
||||
gKeys := make([]string, len(keys))
|
||||
for k, v := range groupKeys {
|
||||
sort.Strings(v)
|
||||
gKeys = append(gKeys, k)
|
||||
}
|
||||
rs := groupSorter{gKeys, v.sortCol.asc}
|
||||
sort.Sort(rs)
|
||||
|
||||
for _, gk := range gKeys {
|
||||
for _, k := range groupKeys[gk] {
|
||||
prim, sec := v.sortAllRows(data.Rows, sortFn)
|
||||
for _, pk := range prim {
|
||||
for _, sk := range sec[pk] {
|
||||
fgColor := tcell.ColorGray
|
||||
if v.colorerFn != nil {
|
||||
fgColor = v.colorerFn(data.Namespace, data.Rows[k])
|
||||
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
||||
}
|
||||
for col, field := range data.Rows[k].Fields {
|
||||
v.addBodyCell(row, col, field, data.Rows[k].Deltas[col], fgColor)
|
||||
for col, field := range data.Rows[sk].Fields {
|
||||
v.addBodyCell(row, col, field, data.Rows[sk].Deltas[col], fgColor, pads)
|
||||
}
|
||||
row++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *tableView) addHeaderCell(col int, name string) {
|
||||
c := tview.NewTableCell(v.displayCol(col, name))
|
||||
func (v *tableView) sortAllRows(rows resource.RowEvents, sortFn sortFn) (resource.Row, map[string]resource.Row) {
|
||||
keys := make([]string, len(rows))
|
||||
v.sortRows(rows, sortFn, v.sortCol, keys)
|
||||
|
||||
sec := make(map[string]resource.Row, len(rows))
|
||||
for _, k := range keys {
|
||||
grp := rows[k].Fields[v.sortCol.index]
|
||||
log.Debug().Msg("append")
|
||||
sec[grp] = append(sec[grp], k)
|
||||
}
|
||||
|
||||
// Performs secondary to sort by name for each groups.
|
||||
prim := make(resource.Row, 0, len(sec))
|
||||
for k, v := range sec {
|
||||
sort.Strings(v)
|
||||
prim = append(prim, k)
|
||||
}
|
||||
rs := groupSorter{prim, v.sortCol.asc}
|
||||
sort.Sort(rs)
|
||||
|
||||
return prim, sec
|
||||
}
|
||||
|
||||
func (v *tableView) addHeaderCell(col int, name string, pads maxyPad) {
|
||||
c := tview.NewTableCell(v.sortIndicator(col, name))
|
||||
{
|
||||
c.SetExpansion(3)
|
||||
if len(name) == 0 {
|
||||
c.SetExpansion(1)
|
||||
}
|
||||
c.SetExpansion(1)
|
||||
c.SetTextColor(tcell.ColorAntiqueWhite)
|
||||
}
|
||||
v.SetCell(0, col, c)
|
||||
}
|
||||
|
||||
func (v *tableView) addBodyCell(row, col int, field, delta string, color tcell.Color) {
|
||||
c := tview.NewTableCell(deltas(delta, field))
|
||||
func (v *tableView) addBodyCell(row, col int, field, delta string, color tcell.Color, pads maxyPad) {
|
||||
var pField string
|
||||
if isASCII(field) {
|
||||
pField = pad(deltas(delta, field), pads[col])
|
||||
} else {
|
||||
pField = deltas(delta, field)
|
||||
}
|
||||
|
||||
c := tview.NewTableCell(pField)
|
||||
{
|
||||
c.SetExpansion(3)
|
||||
if len(v.GetCell(0, col).Text) == 0 {
|
||||
c.SetExpansion(1)
|
||||
}
|
||||
c.SetExpansion(1)
|
||||
c.SetTextColor(color)
|
||||
}
|
||||
v.SetCell(row, col, c)
|
||||
|
|
|
|||
Loading…
Reference in New Issue