parent
f469070b35
commit
3357b1237b
|
|
@ -0,0 +1,27 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.7.3
|
||||
|
||||
## Notes
|
||||
|
||||
Thank you to all that contributed with flushing out issues with K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as always very much appreciated!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
---
|
||||
|
||||
## Change Logs
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Resolved Bugs/Features
|
||||
|
||||
+ [Issue #210](https://github.com/derailed/k9s/issues/210)
|
||||
+ [Issue #209](https://github.com/derailed/k9s/issues/209)
|
||||
+ [Issue #206](https://github.com/derailed/k9s/issues/206) Thank you @carlowouters!!
|
||||
|
||||
|
||||
---
|
||||
|
||||
<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)
|
||||
|
|
@ -191,7 +191,7 @@ func newTable() *Table {
|
|||
func newTableHeader() *TableHeader {
|
||||
return &TableHeader{
|
||||
FgColor: "white",
|
||||
BgColor: "black",
|
||||
BgColor: "red",
|
||||
SorterColor: "aqua",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,23 +80,6 @@ func (r *Resource) listAll(ns, n string) (runtime.Object, error) {
|
|||
Do().Get()
|
||||
}
|
||||
|
||||
func (r *Resource) getOne(ns, n string) (runtime.Object, error) {
|
||||
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||
_, codec := r.codecs()
|
||||
|
||||
c, err := r.getClient()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.Get().
|
||||
SetHeader("Accept", a).
|
||||
Namespace(ns).
|
||||
Resource(n).
|
||||
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||
Do().Get()
|
||||
}
|
||||
|
||||
func (r *Resource) getClient() (*rest.RESTClient, error) {
|
||||
crConfig := r.RestConfigOrDie()
|
||||
crConfig.GroupVersion = &schema.GroupVersion{Group: r.group, Version: r.version}
|
||||
|
|
|
|||
|
|
@ -287,9 +287,9 @@ func toRes(r v1.ResourceList) (string, string) {
|
|||
|
||||
func probe(p *v1.Probe) string {
|
||||
if p == nil {
|
||||
return "on"
|
||||
return "off"
|
||||
}
|
||||
return "off"
|
||||
return "on"
|
||||
}
|
||||
|
||||
func asMi(v int64) float64 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,110 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
)
|
||||
|
||||
func TestProbe(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
probe *v1.Probe
|
||||
e string
|
||||
}{
|
||||
"defined": {&v1.Probe{}, "on"},
|
||||
"undefined": {nil, "off"},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, probe(u.probe))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsMi(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
mem int64
|
||||
e float64
|
||||
}{
|
||||
"zero": {0, 0},
|
||||
"1Mb": {1024 * 1024, 1.048576e+06},
|
||||
"10Mb": {10 * 1024 * 1024, 1.048576e+07},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, asMi(u.mem))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToRes(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
res v1.ResourceList
|
||||
ecpu, emem string
|
||||
}{
|
||||
"cool": {v1.ResourceList{
|
||||
v1.ResourceCPU: toQty("10m"),
|
||||
v1.ResourceMemory: toQty("20Mi"),
|
||||
},
|
||||
"10", "20"},
|
||||
"noRes": {v1.ResourceList{},
|
||||
"0", "0"},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
cpu, mem := toRes(u.res)
|
||||
assert.Equal(t, u.ecpu, cpu)
|
||||
assert.Equal(t, u.emem, mem)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestToState(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
state v1.ContainerState
|
||||
e string
|
||||
}{
|
||||
"empty": {v1.ContainerState{},
|
||||
MissingValue},
|
||||
"running": {
|
||||
v1.ContainerState{Running: &v1.ContainerStateRunning{}},
|
||||
"Running",
|
||||
},
|
||||
"waiting": {
|
||||
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{}},
|
||||
"Waiting",
|
||||
},
|
||||
"waitingReason": {
|
||||
v1.ContainerState{Waiting: &v1.ContainerStateWaiting{Reason: "blee"}},
|
||||
"blee",
|
||||
},
|
||||
"terminated": {
|
||||
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{}},
|
||||
"Terminated",
|
||||
},
|
||||
"terminatedReason": {
|
||||
v1.ContainerState{Terminated: &v1.ContainerStateTerminated{Reason: "blee"}},
|
||||
"blee",
|
||||
},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
assert.Equal(t, u.e, toState(u.state))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func toQty(s string) resource.Quantity {
|
||||
q, _ := resource.ParseQuantity(s)
|
||||
|
||||
return q
|
||||
}
|
||||
|
|
@ -94,7 +94,7 @@ func (r *Custom) List(ns string) (Columnars, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(ii) != 1 {
|
||||
if len(ii) == 0 {
|
||||
return Columnars{}, errors.New("no resources found")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ func newContainerView(t string, app *appView, list resource.List, path string, e
|
|||
{
|
||||
v.path = &path
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.viewLogs
|
||||
v.colorerFn = containerColorer
|
||||
v.current = app.content.GetPrimitive("main").(igniter)
|
||||
v.exitFn = exitFn
|
||||
|
|
@ -45,7 +46,6 @@ func (v *containerView) extraActions(aa keyActions) {
|
|||
aa[KeyS] = newKeyAction("Shell", v.shellCmd, true)
|
||||
aa[tcell.KeyEscape] = newKeyAction("Back", v.backCmd, false)
|
||||
aa[KeyP] = newKeyAction("Previous", v.backCmd, false)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Logs", v.logsCmd, false)
|
||||
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(6, false), true)
|
||||
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(7, false), true)
|
||||
aa[KeyAltC] = newKeyAction("Sort CPU%", v.sortColCmd(8, false), true)
|
||||
|
|
@ -72,18 +72,22 @@ func (v *containerView) getSelection() string {
|
|||
|
||||
// Handlers...
|
||||
|
||||
func (v *containerView) viewLogs(app *appView, _, res, sel string) {
|
||||
cell := v.getTV().GetCell(v.selectedRow, 3)
|
||||
if cell != nil && strings.TrimSpace(cell.Text) != "Running" {
|
||||
v.app.flash().err(errors.New("No logs for a non running container"))
|
||||
return
|
||||
}
|
||||
|
||||
v.showLogs(sel, v.list.GetName(), v, false)
|
||||
}
|
||||
|
||||
func (v *containerView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
cell := v.getTV().GetCell(v.selectedRow, 3)
|
||||
if cell != nil && strings.TrimSpace(cell.Text) != "Running" {
|
||||
v.app.flash().err(errors.New("No logs for a non running container"))
|
||||
return evt
|
||||
}
|
||||
v.showLogs(v.selectedItem, v.list.GetName(), v, false)
|
||||
|
||||
v.viewLogs(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type contextView struct {
|
||||
|
|
@ -14,6 +13,7 @@ type contextView struct {
|
|||
func newContextView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := contextView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.useCtx
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
|
||||
return &v
|
||||
|
|
@ -21,21 +21,14 @@ func newContextView(t string, app *appView, list resource.List) resourceViewer {
|
|||
|
||||
func (v *contextView) extraActions(aa keyActions) {
|
||||
delete(v.getTV().actions, KeyShiftA)
|
||||
aa[tcell.KeyEnter] = newKeyAction("Switch", v.useCmd, true)
|
||||
}
|
||||
|
||||
func (v *contextView) useCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
func (v *contextView) useCtx(app *appView, _, res, sel string) {
|
||||
if err := v.useContext(sel); err != nil {
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
if err := v.useContext(v.selectedItem); err != nil {
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
}
|
||||
|
||||
v.app.gotoResource("po", true)
|
||||
|
||||
return nil
|
||||
app.gotoResource("po", true)
|
||||
}
|
||||
|
||||
func (*contextView) cleanser(s string) string {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type deployView struct {
|
|||
func newDeployView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := deployView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -23,7 +24,6 @@ func newDeployView(t string, app *appView, list resource.List) resourceViewer {
|
|||
func (v *deployView) extraActions(aa keyActions) {
|
||||
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
|
||||
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *deployView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -36,30 +36,25 @@ func (v *deployView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tc
|
|||
}
|
||||
}
|
||||
|
||||
func (v *deployView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
d := k8s.NewDeployment(v.app.conn())
|
||||
func (v *deployView) showPods(app *appView, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
d := k8s.NewDeployment(app.conn())
|
||||
dep, err := d.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching Deployment %s", v.selectedItem)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Fetching Deployment %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
|
||||
dp := dep.(*v1.Deployment)
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
|
||||
l, err := metav1.LabelSelectorAsSelector(dp.Spec.Selector)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Converting selector for Deployment %s", v.selectedItem)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Converting selector for Deployment %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
showPods(v.app, ns, "Deployment", v.selectedItem, sel.String(), "", v.backCmd)
|
||||
|
||||
return nil
|
||||
showPods(app, ns, "Deployment", sel, l.String(), "", v.backCmd)
|
||||
}
|
||||
|
||||
func (v *deployView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type daemonSetView struct {
|
|||
func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := daemonSetView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -23,7 +24,6 @@ func newDaemonSetView(t string, app *appView, list resource.List) resourceViewer
|
|||
func (v *daemonSetView) extraActions(aa keyActions) {
|
||||
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
|
||||
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *daemonSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -36,30 +36,25 @@ func (v *daemonSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey)
|
|||
}
|
||||
}
|
||||
|
||||
func (v *daemonSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
d := k8s.NewDaemonSet(v.app.conn())
|
||||
func (v *daemonSetView) showPods(app *appView, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
d := k8s.NewDaemonSet(app.conn())
|
||||
dset, err := d.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching DeaemonSet %s", v.selectedItem)
|
||||
log.Error().Err(err).Msgf("Fetching DeaemonSet %s", sel)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
return
|
||||
}
|
||||
|
||||
ds := dset.(*extv1beta1.DaemonSet)
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
|
||||
l, err := metav1.LabelSelectorAsSelector(ds.Spec.Selector)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", v.selectedItem)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Converting selector for DaemonSet %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
showPods(v.app, ns, "DaemonSet", v.selectedItem, sel.String(), "", v.backCmd)
|
||||
|
||||
return nil
|
||||
showPods(app, ns, "DaemonSet", sel, l.String(), "", v.backCmd)
|
||||
}
|
||||
|
||||
func (v *daemonSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ func (v *dumpView) registerActions() {
|
|||
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
|
||||
v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, true)
|
||||
v.actions[tcell.KeyCtrlD] = newKeyAction("Delete", v.deleteCmd, true)
|
||||
v.actions[tcell.KeyCtrlS] = newKeyAction("Save", v.app.noopCmd, false)
|
||||
|
||||
vu := v.getTV()
|
||||
vu.setActions(v.actions)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
|
|
@ -106,7 +105,6 @@ func TestStripPort(t *testing.T) {
|
|||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
fmt.Println("TCP?", u.port, isTCPPort(u.port))
|
||||
assert.Equal(t, u.e, stripPort(u.port))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type jobView struct {
|
|||
func newJobView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := jobView{resourceView: newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
|
||||
|
||||
picker := newSelectList(&v)
|
||||
|
|
@ -99,33 +100,27 @@ func (v *jobView) showLogs(path, co, view string, parent loggable, prev bool) {
|
|||
func (v *jobView) extraActions(aa keyActions) {
|
||||
aa[KeyL] = newKeyAction("Logs", v.logsCmd, true)
|
||||
aa[KeyShiftL] = newKeyAction("Logs Previous", v.prevLogsCmd, true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *jobView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
j := k8s.NewJob(v.app.conn())
|
||||
func (v *jobView) showPods(app *appView, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
j := k8s.NewJob(app.conn())
|
||||
job, err := j.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching Job %s", v.selectedItem)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Fetching Job %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
|
||||
jo := job.(*batchv1.Job)
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
|
||||
l, err := metav1.LabelSelectorAsSelector(jo.Spec.Selector)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Converting selector for Job %s", v.selectedItem)
|
||||
v.app.flash().err(err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Converting selector for Job %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
showPods(v.app, "", "Job", v.selectedItem, sel.String(), "", v.backCmd)
|
||||
|
||||
return nil
|
||||
showPods(app, "", "Job", sel, l.String(), "", v.backCmd)
|
||||
}
|
||||
|
||||
func (v *jobView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type nodeView struct {
|
|||
func newNodeView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := nodeView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -19,7 +20,6 @@ func newNodeView(t string, app *appView, list resource.List) resourceViewer {
|
|||
func (v *nodeView) extraActions(aa keyActions) {
|
||||
aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, false), true)
|
||||
aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(8, false), true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -32,14 +32,8 @@ func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcel
|
|||
}
|
||||
}
|
||||
|
||||
func (v *nodeView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
showPods(v.app, "", "Node", v.selectedItem, "", "spec.nodeName="+v.selectedItem, v.backCmd)
|
||||
|
||||
return nil
|
||||
func (v *nodeView) showPods(app *appView, _, res, sel string) {
|
||||
showPods(app, "", "Node", sel, "", "spec.nodeName="+sel, v.backCmd)
|
||||
}
|
||||
|
||||
func (v *nodeView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -25,24 +25,19 @@ func newNamespaceView(t string, app *appView, list resource.List) resourceViewer
|
|||
v.extraActionsFn = v.extraActions
|
||||
v.selectedFn = v.getSelectedItem
|
||||
v.decorateFn = v.decorate
|
||||
v.enterFn = v.switchNs
|
||||
v.getTV().cleanseFn = v.cleanser
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
func (v *namespaceView) extraActions(aa keyActions) {
|
||||
aa[tcell.KeyEnter] = newKeyAction("Switch", v.switchNsCmd, true)
|
||||
aa[KeyU] = newKeyAction("Use", v.useNsCmd, true)
|
||||
}
|
||||
|
||||
func (v *namespaceView) switchNsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
v.useNamespace(v.getSelectedItem())
|
||||
v.app.gotoResource("po", true)
|
||||
|
||||
return nil
|
||||
func (v *namespaceView) switchNs(app *appView, _, res, sel string) {
|
||||
v.useNamespace(sel)
|
||||
app.gotoResource("po", true)
|
||||
}
|
||||
|
||||
func (v *namespaceView) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
|
|||
}
|
||||
|
||||
var row int
|
||||
for k, rev := range table.Rows {
|
||||
for _, rev := range table.Rows {
|
||||
ageIndex := len(rev.Fields) - 1
|
||||
for index, field := range rev.Fields {
|
||||
// Date field comes out as timestamp.
|
||||
|
|
@ -31,7 +31,6 @@ func computeMaxColumns(pads maxyPad, sortCol int, table resource.TableData) {
|
|||
if err == nil {
|
||||
field = duration.HumanDuration(dur)
|
||||
}
|
||||
table.Rows[k].Fields[index] = field
|
||||
}
|
||||
width := len(field) + colPadding
|
||||
if width > pads[index] {
|
||||
|
|
|
|||
|
|
@ -64,20 +64,17 @@ func (v *podView) extraActions(aa keyActions) {
|
|||
}
|
||||
|
||||
func (v *podView) listContainers(app *appView, _, res, sel string) {
|
||||
if !v.rowSelected() {
|
||||
return
|
||||
}
|
||||
|
||||
po, err := v.app.informer.Get(watch.PodIndex, sel, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Unable to retrieve pod %s", sel)
|
||||
app.flash().errf("Unable to retrieve pods %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
pod := po.(*v1.Pod)
|
||||
mx := k8s.NewMetricsServer(app.conn())
|
||||
list := resource.NewContainerList(app.conn(), mx, pod)
|
||||
title := skinTitle(fmt.Sprintf(containerFmt, "Containers", sel), v.app.styles.Style)
|
||||
title := skinTitle(fmt.Sprintf(containerFmt, "Containers", sel), app.styles.Style)
|
||||
|
||||
// Stop my updater
|
||||
if v.cancelFn != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type portSelector struct {
|
||||
title, port string
|
||||
ok, cancel func()
|
||||
}
|
||||
|
||||
func newSelector(title, port string, okFn, cancelFn func()) *portSelector {
|
||||
return &portSelector{
|
||||
title: title,
|
||||
port: port,
|
||||
ok: okFn,
|
||||
cancel: cancelFn,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *portSelector) show(app *appView) {
|
||||
f := tview.NewForm()
|
||||
f.SetItemPadding(0)
|
||||
f.SetButtonsAlign(tview.AlignCenter).
|
||||
SetButtonBackgroundColor(tview.Styles.PrimitiveBackgroundColor).
|
||||
SetButtonTextColor(tview.Styles.PrimaryTextColor).
|
||||
SetLabelColor(tcell.ColorAqua).
|
||||
SetFieldTextColor(tcell.ColorOrange)
|
||||
|
||||
f1 := p.port
|
||||
f.AddInputField("Pod Port:", f1, 20, nil, func(changed string) {
|
||||
f1 = changed
|
||||
})
|
||||
|
||||
f.AddButton("OK", p.ok)
|
||||
f.AddButton("Cancel", p.cancel)
|
||||
|
||||
modal := tview.NewModalForm("<"+p.title+">", f)
|
||||
modal.SetDoneFunc(func(_ int, b string) {
|
||||
p.cancel()
|
||||
})
|
||||
}
|
||||
|
|
@ -185,6 +185,11 @@ func (v *resourceView) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if v.getTV().filterCmd(evt) == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.selectedItem == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if v.enterFn != nil {
|
||||
v.enterFn(v.app, v.list.GetNamespace(), v.list.GetName(), v.selectedItem)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ type replicaSetView struct {
|
|||
func newReplicaSetView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := replicaSetView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -31,7 +32,6 @@ func (v *replicaSetView) extraActions(aa keyActions) {
|
|||
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(2, false), true)
|
||||
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(3, false), true)
|
||||
aa[tcell.KeyCtrlB] = newKeyAction("Rollback", v.rollbackCmd, true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *replicaSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -44,30 +44,24 @@ func (v *replicaSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey)
|
|||
}
|
||||
}
|
||||
|
||||
func (v *replicaSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
rset := k8s.NewReplicaSet(v.app.conn())
|
||||
func (v *replicaSetView) showPods(app *appView, ns, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
rset := k8s.NewReplicaSet(app.conn())
|
||||
r, err := rset.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching ReplicaSet %s", v.selectedItem)
|
||||
v.app.flash().errf("Replicaset failed %s", err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Fetching ReplicaSet %s", sel)
|
||||
app.flash().errf("Replicaset failed %s", err)
|
||||
}
|
||||
|
||||
rs := r.(*v1.ReplicaSet)
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
|
||||
l, err := metav1.LabelSelectorAsSelector(rs.Spec.Selector)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", v.selectedItem)
|
||||
v.app.flash().errf("Selector failed %s", err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Converting selector for ReplicaSet %s", sel)
|
||||
app.flash().errf("Selector failed %s", err)
|
||||
return
|
||||
}
|
||||
showPods(v.app, "", "ReplicaSet", v.selectedItem, sel.String(), "", v.backCmd)
|
||||
|
||||
return nil
|
||||
showPods(app, "", "ReplicaSet", sel, l.String(), "", v.backCmd)
|
||||
}
|
||||
|
||||
func (v *replicaSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -70,9 +70,9 @@ func less(asc bool, c1, c2 string) bool {
|
|||
return !b
|
||||
}
|
||||
|
||||
func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
|
||||
d1, ok1 := isDuration(c1)
|
||||
d2, ok2 := isDuration(c2)
|
||||
func isDurationSort(asc bool, s1, s2 string) (bool, bool) {
|
||||
d1, ok1 := isDuration(s1)
|
||||
d2, ok2 := isDuration(s2)
|
||||
if !ok1 || !ok2 {
|
||||
return false, false
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ func isDurationSort(asc bool, c1, c2 string) (bool, bool) {
|
|||
if asc {
|
||||
return d1 <= d2, true
|
||||
}
|
||||
return d1 > d2, true
|
||||
return d1 >= d2, true
|
||||
}
|
||||
|
||||
func isMetricSort(asc bool, c1, c2 string) (bool, bool) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ func TestGroupSort(t *testing.T) {
|
|||
{true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}},
|
||||
{false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}},
|
||||
{true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}},
|
||||
{true, []string{"3y37d", "2y4d"}, []string{"2y4d", "3y37d"}},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
|
|
@ -80,3 +81,24 @@ func TestRowSort(t *testing.T) {
|
|||
assert.Equal(t, u.expect, r.rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDurationSort(t *testing.T) {
|
||||
uu := map[string]struct {
|
||||
s1, s2 string
|
||||
asc, e bool
|
||||
}{
|
||||
"ascLess": {"10h5m", "2h10m", true, false},
|
||||
"descGreater": {"10h5m", "2h10m", false, true},
|
||||
"ascEqual": {"2h10m", "2h10m", true, true},
|
||||
"descEqual": {"2h10m", "2h10m", false, true},
|
||||
"ascGreater": {"10h10m", "2h5m", true, false},
|
||||
}
|
||||
|
||||
for k, u := range uu {
|
||||
t.Run(k, func(t *testing.T) {
|
||||
less, ok := isDurationSort(u.asc, u.s1, u.s2)
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, u.e, less)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ type statefulSetView struct {
|
|||
func newStatefulSetView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := statefulSetView{newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -23,7 +24,6 @@ func newStatefulSetView(t string, app *appView, list resource.List) resourceView
|
|||
func (v *statefulSetView) extraActions(aa keyActions) {
|
||||
aa[KeyShiftD] = newKeyAction("Sort Desired", v.sortColCmd(1, false), true)
|
||||
aa[KeyShiftC] = newKeyAction("Sort Current", v.sortColCmd(2, false), true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
}
|
||||
|
||||
func (v *statefulSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -36,30 +36,25 @@ func (v *statefulSetView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey
|
|||
}
|
||||
}
|
||||
|
||||
func (v *statefulSetView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
d := k8s.NewStatefulSet(v.app.conn())
|
||||
s, err := d.Get(ns, n)
|
||||
func (v *statefulSetView) showPods(app *appView, ns, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
s := k8s.NewStatefulSet(app.conn())
|
||||
st, err := s.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetching StatefulSet %s", v.selectedItem)
|
||||
v.app.flash().errf("Unable to fetch statefulset %s", err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Fetching StatefulSet %s", sel)
|
||||
app.flash().errf("Unable to fetch statefulset %s", err)
|
||||
return
|
||||
}
|
||||
sts := s.(*v1.StatefulSet)
|
||||
|
||||
sel, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
|
||||
sts := st.(*v1.StatefulSet)
|
||||
l, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", v.selectedItem)
|
||||
v.app.flash().errf("Selector failed %s", err)
|
||||
return evt
|
||||
log.Error().Err(err).Msgf("Converting selector for StatefulSet %s", sel)
|
||||
app.flash().errf("Selector failed %s", err)
|
||||
return
|
||||
}
|
||||
showPods(v.app, "", "StatefulSet", v.selectedItem, sel.String(), "", v.backCmd)
|
||||
|
||||
return nil
|
||||
showPods(app, "", "StatefulSet", sel, l.String(), "", v.backCmd)
|
||||
}
|
||||
|
||||
func (v *statefulSetView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ type svcView struct {
|
|||
func newSvcView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := svcView{resourceView: newResourceView(t, app, list).(*resourceView)}
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.showPods
|
||||
|
||||
return &v
|
||||
}
|
||||
|
|
@ -29,7 +30,6 @@ func newSvcView(t string, app *appView, list resource.List) resourceViewer {
|
|||
func (v *svcView) extraActions(aa keyActions) {
|
||||
aa[tcell.KeyCtrlB] = newKeyAction("Bench", v.benchCmd, true)
|
||||
aa[KeyAltB] = newKeyAction("Bench Stop", v.benchStopCmd, true)
|
||||
aa[tcell.KeyEnter] = newKeyAction("View Pods", v.showPodsCmd, true)
|
||||
|
||||
aa[KeyShiftT] = newKeyAction("Sort Type", v.sortColCmd(1, false), true)
|
||||
}
|
||||
|
|
@ -44,23 +44,19 @@ func (v *svcView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell
|
|||
}
|
||||
}
|
||||
|
||||
func (v *svcView) showPodsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !v.rowSelected() {
|
||||
return evt
|
||||
}
|
||||
|
||||
s := k8s.NewService(v.app.conn())
|
||||
ns, n := namespaced(v.selectedItem)
|
||||
res, err := s.Get(ns, n)
|
||||
func (v *svcView) showPods(app *appView, ns, res, sel string) {
|
||||
s := k8s.NewService(app.conn())
|
||||
ns, n := namespaced(sel)
|
||||
svc, err := s.Get(ns, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Fetch service %s", v.selectedItem)
|
||||
return nil
|
||||
}
|
||||
if svc, ok := res.(*v1.Service); ok {
|
||||
v.showSvcPods(ns, svc.Spec.Selector, v.backCmd)
|
||||
log.Error().Err(err).Msgf("Fetch service %s", sel)
|
||||
app.flash().err(err)
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
if s, ok := svc.(*v1.Service); ok {
|
||||
v.showSvcPods(ns, s.Spec.Selector, v.backCmd)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *svcView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -111,15 +107,12 @@ func (v *svcView) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
svcType := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+1).Text)
|
||||
log.Debug().Msgf("Service Type %q", svcType)
|
||||
if svcType != "NodePort" && svcType != "LoadBalancer" {
|
||||
v.app.flash().err(errors.New("You must select a reachable service"))
|
||||
return nil
|
||||
}
|
||||
|
||||
ports := strings.TrimSpace(tv.GetCell(r, tv.nameColIndex()+5).Text)
|
||||
// BOZO!! You Brute!!
|
||||
// BOZO!! Will new much improv ie pop dialog and select port if multiport.
|
||||
pp := strings.Split(ports, " ")
|
||||
if len(pp) == 0 {
|
||||
v.app.flash().err(errors.New("No ports found"))
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
"k8s.io/apimachinery/pkg/util/duration"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -421,16 +422,23 @@ func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string,
|
|||
c := tview.NewTableCell(v.sortIndicator(col, name))
|
||||
{
|
||||
c.SetExpansion(1)
|
||||
c.SetTextColor(fg)
|
||||
if numCols[name] || cpuRX.MatchString(name) || memRX.MatchString(name) {
|
||||
c.SetAlign(tview.AlignRight)
|
||||
}
|
||||
c.SetTextColor(fg)
|
||||
c.SetBackgroundColor(bg)
|
||||
}
|
||||
v.SetCell(0, col, c)
|
||||
}
|
||||
|
||||
func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col int, field, delta string, color tcell.Color, pads maxyPad) {
|
||||
if header == "AGE" {
|
||||
dur, err := time.ParseDuration(field)
|
||||
if err == nil {
|
||||
field = duration.HumanDuration(dur)
|
||||
}
|
||||
}
|
||||
|
||||
field += deltas(delta, field)
|
||||
align := tview.AlignLeft
|
||||
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,16 @@ func TestTVSortRows(t *testing.T) {
|
|||
resource.Row{"x", "y"},
|
||||
[]string{"row1", "row2"},
|
||||
},
|
||||
{
|
||||
resource.RowEvents{
|
||||
"row1": {Fields: resource.Row{"2175h48m0.06015s", "y"}},
|
||||
"row2": {Fields: resource.Row{"403h42m34.060166s", "b"}},
|
||||
},
|
||||
0,
|
||||
true,
|
||||
resource.Row{"403h42m34.060166s", "b"},
|
||||
[]string{"row2", "row1"},
|
||||
},
|
||||
}
|
||||
|
||||
var v *tableView
|
||||
|
|
|
|||
Loading…
Reference in New Issue