misc bug fixes and cleanup
parent
ee61a1b578
commit
f770348f74
|
|
@ -0,0 +1,30 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||
|
||||
# Release v0.5.2
|
||||
|
||||
## 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.
|
||||
|
||||
Thank you so much for your support and awesome suggestions to make K9s better!!
|
||||
|
||||
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||
|
||||
---
|
||||
|
||||
## Change Logs
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Resolved Bugs
|
||||
|
||||
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
|
||||
+ [Issue #173](https://github.com/derailed/k9s/issues/173)
|
||||
+ [Issue #174](https://github.com/derailed/k9s/issues/174)
|
||||
|
||||
---
|
||||
|
||||
<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)
|
||||
|
|
@ -135,7 +135,11 @@ func (a *APIClient) IsNamespaced(res string) bool {
|
|||
|
||||
// SupportsResource checks for resource supported version against the server.
|
||||
func (a *APIClient) SupportsResource(group string) bool {
|
||||
list, _ := a.DialOrDie().Discovery().ServerPreferredResources()
|
||||
list, err := a.DialOrDie().Discovery().ServerPreferredResources()
|
||||
if err != nil {
|
||||
log.Debug().Err(err).Msg("Unable to dial api server")
|
||||
return false
|
||||
}
|
||||
for _, l := range list {
|
||||
log.Debug().Msgf(">>> Group %s", l.GroupVersion)
|
||||
if l.GroupVersion == group {
|
||||
|
|
|
|||
|
|
@ -115,8 +115,6 @@ func (b *Base) Describe(kind, pa string, flags *genericclioptions.ConfigFlags) (
|
|||
return "", err
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Describer %#v", d)
|
||||
|
||||
return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ func (r *Container) Fields(ns string) Row {
|
|||
}
|
||||
cs = &c
|
||||
}
|
||||
|
||||
if cs == nil {
|
||||
for _, c := range r.pod.Status.InitContainerStatuses {
|
||||
if c.Name != i.Name {
|
||||
|
|
@ -188,12 +189,17 @@ func (r *Container) Fields(ns string) Row {
|
|||
}
|
||||
}
|
||||
|
||||
ready, state, restarts := "false", MissingValue, "0"
|
||||
if cs != nil {
|
||||
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount))
|
||||
}
|
||||
|
||||
return append(ff,
|
||||
i.Name,
|
||||
i.Image,
|
||||
boolToStr(cs.Ready),
|
||||
toState(cs.State),
|
||||
strconv.Itoa(int(cs.RestartCount)),
|
||||
ready,
|
||||
state,
|
||||
restarts,
|
||||
probe(i.LivenessProbe),
|
||||
probe(i.ReadinessProbe),
|
||||
cpu,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/rs/zerolog/log"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/kubernetes/pkg/util/node"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -212,7 +213,7 @@ func (r *Pod) Fields(ns string) Row {
|
|||
return append(ff,
|
||||
i.ObjectMeta.Name,
|
||||
strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)),
|
||||
r.phase(i.Status),
|
||||
r.phase(i),
|
||||
strconv.Itoa(rc),
|
||||
ToMillicore(r.metrics.CurrentCPU),
|
||||
ToMi(r.metrics.CurrentMEM),
|
||||
|
|
@ -251,21 +252,76 @@ func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
|
|||
return
|
||||
}
|
||||
|
||||
func (*Pod) phase(s v1.PodStatus) string {
|
||||
status := "Pending"
|
||||
for _, cs := range s.ContainerStatuses {
|
||||
func isSet(s *string) bool {
|
||||
return s != nil && *s != ""
|
||||
}
|
||||
|
||||
func (*Pod) phase(po *v1.Pod) string {
|
||||
status := string(po.Status.Phase)
|
||||
if po.Status.Reason != "" {
|
||||
if po.DeletionTimestamp != nil && po.Status.Reason == node.NodeUnreachablePodReason {
|
||||
return "Unknown"
|
||||
}
|
||||
status = po.Status.Reason
|
||||
}
|
||||
|
||||
var init bool
|
||||
for i, cs := range po.Status.InitContainerStatuses {
|
||||
switch {
|
||||
case cs.State.Running != nil:
|
||||
status = "Running"
|
||||
case cs.State.Waiting != nil:
|
||||
status = cs.State.Waiting.Reason
|
||||
case cs.State.Terminated != nil:
|
||||
status = "Terminating"
|
||||
if len(cs.State.Terminated.Reason) != 0 {
|
||||
status = cs.State.Terminated.Reason
|
||||
if cs.State.Terminated.ExitCode == 0 {
|
||||
continue
|
||||
}
|
||||
if cs.State.Terminated.Reason != "" {
|
||||
status = "Init:" + cs.State.Terminated.Reason
|
||||
init = true
|
||||
break
|
||||
}
|
||||
|
||||
if cs.State.Terminated.Signal != 0 {
|
||||
status = fmt.Sprintf("Init:Signal:%d", cs.State.Terminated.Signal)
|
||||
} else {
|
||||
status = fmt.Sprintf("Init:ExitCode:%d", cs.State.Terminated.ExitCode)
|
||||
}
|
||||
case cs.State.Waiting != nil && cs.State.Waiting.Reason != "" && cs.State.Waiting.Reason != "PodInitializing":
|
||||
status = "Init:" + cs.State.Waiting.Reason
|
||||
default:
|
||||
status = fmt.Sprintf("Init:%d/%d", i, len(po.Spec.InitContainers))
|
||||
}
|
||||
init = true
|
||||
break
|
||||
}
|
||||
|
||||
if init {
|
||||
return status
|
||||
}
|
||||
|
||||
var running bool
|
||||
for i := len(po.Status.ContainerStatuses) - 1; i >= 0; i-- {
|
||||
cs := po.Status.ContainerStatuses[i]
|
||||
switch {
|
||||
case cs.State.Waiting != nil && cs.State.Waiting.Reason != "":
|
||||
status = cs.State.Waiting.Reason
|
||||
case cs.State.Terminated != nil && cs.State.Terminated.Reason != "":
|
||||
status = cs.State.Terminated.Reason
|
||||
case cs.State.Terminated != nil:
|
||||
if cs.State.Terminated.Signal != 0 {
|
||||
status = fmt.Sprintf("Signal:%d", cs.State.Terminated.Signal)
|
||||
} else {
|
||||
status = fmt.Sprintf("ExitCode:%d", cs.State.Terminated.ExitCode)
|
||||
}
|
||||
case cs.Ready && cs.State.Running != nil:
|
||||
running = true
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
if status == "Completed" && running {
|
||||
status = "Running"
|
||||
}
|
||||
|
||||
if po.DeletionTimestamp == nil {
|
||||
return status
|
||||
}
|
||||
|
||||
return "Terminated"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package resource
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestPodStatuses(t *testing.T) {
|
||||
|
|
@ -49,69 +51,132 @@ func TestPodStatuses(t *testing.T) {
|
|||
|
||||
func TestPodPhase(t *testing.T) {
|
||||
uu := []struct {
|
||||
s v1.PodStatus
|
||||
p *v1.Pod
|
||||
e string
|
||||
}{
|
||||
{
|
||||
v1.PodStatus{
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
State: v1.ContainerState{
|
||||
Running: &v1.ContainerStateRunning{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Running",
|
||||
},
|
||||
{
|
||||
v1.PodStatus{
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
Reason: "blee",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"blee",
|
||||
},
|
||||
{
|
||||
v1.PodStatus{
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
State: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"Terminating",
|
||||
},
|
||||
{
|
||||
v1.PodStatus{
|
||||
ContainerStatuses: []v1.ContainerStatus{
|
||||
{
|
||||
Name: "c1",
|
||||
State: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
Reason: "blee",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"blee",
|
||||
},
|
||||
{makePodStatus("p1", v1.PodRunning, ""), "Running"},
|
||||
{makePodStatus("p1", v1.PodRunning, "Evicted"), "Evicted"},
|
||||
{makePodStatus("p1", v1.PodPending, ""), "Pending"},
|
||||
{makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"},
|
||||
{makePodStatus("p1", v1.PodFailed, ""), "Failed"},
|
||||
{makePodStatus("p1", v1.PodUnknown, ""), "Unknown"},
|
||||
{makePodCoInitTerminated("p1"), "Init:OOMKilled"},
|
||||
{makePodCoInitWaiting("p1", ""), "Init:0/1"},
|
||||
{makePodCoInitWaiting("p1", "Waiting"), "Init:Waiting"},
|
||||
{makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"},
|
||||
{makePodCoWaiting("p1", "Waiting"), "Waiting"},
|
||||
{makePodCoWaiting("p1", ""), ""},
|
||||
{makePodCoTerminated("p1", "OOMKilled", 0, true), "Terminated"},
|
||||
{makePodCoTerminated("p1", "OOMKilled", 0, false), "OOMKilled"},
|
||||
{makePodCoTerminated("p1", "", 0, true), "Terminated"},
|
||||
{makePodCoTerminated("p1", "", 0, false), "ExitCode:1"},
|
||||
{makePodCoTerminated("p1", "", 1, true), "Terminated"},
|
||||
{makePodCoTerminated("p1", "", 1, false), "Signal:1"},
|
||||
}
|
||||
|
||||
var p Pod
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, p.phase(u.s))
|
||||
assert.Equal(t, u.e, p.phase(u.p))
|
||||
}
|
||||
}
|
||||
|
||||
func makePodStatus(n string, phase v1.PodPhase, reason string) *v1.Pod {
|
||||
po := makePod(n)
|
||||
po.Status = v1.PodStatus{
|
||||
Phase: phase,
|
||||
Reason: reason,
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func makePodCoInitTerminated(n string) *v1.Pod {
|
||||
po := makePod(n)
|
||||
|
||||
po.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{
|
||||
State: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
Reason: "OOMKilled",
|
||||
ExitCode: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func makePodCoInitWaiting(n, reason string) *v1.Pod {
|
||||
po := makePod(n)
|
||||
|
||||
po.Status.InitContainerStatuses = []v1.ContainerStatus{
|
||||
{
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
Reason: reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod {
|
||||
po := makePod(n)
|
||||
|
||||
if deleted {
|
||||
po.DeletionTimestamp = &metav1.Time{time.Now()}
|
||||
}
|
||||
po.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{
|
||||
State: v1.ContainerState{
|
||||
Terminated: &v1.ContainerStateTerminated{
|
||||
Reason: reason,
|
||||
Signal: signal,
|
||||
ExitCode: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func makePodCoWaiting(n, reason string) *v1.Pod {
|
||||
po := makePod(n)
|
||||
|
||||
po.Status.ContainerStatuses = []v1.ContainerStatus{
|
||||
{
|
||||
State: v1.ContainerState{
|
||||
Waiting: &v1.ContainerStateWaiting{
|
||||
Reason: reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return po
|
||||
}
|
||||
|
||||
func makePod(n string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: n,
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
InitContainers: []v1.Container{
|
||||
{
|
||||
Name: "ic1",
|
||||
},
|
||||
},
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "c1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
"sync"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
|
|
@ -60,6 +61,7 @@ type (
|
|||
cmdBuff *cmdBuff
|
||||
cmdView *cmdView
|
||||
actions keyActions
|
||||
mx sync.Mutex
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -143,6 +145,9 @@ func (a *appView) Run() {
|
|||
}
|
||||
|
||||
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||
a.mx.Lock()
|
||||
defer a.mx.Unlock()
|
||||
|
||||
key := evt.Key()
|
||||
if key == tcell.KeyRune {
|
||||
if a.cmdBuff.isActive() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
|
|
@ -24,6 +25,7 @@ type detailsView struct {
|
|||
cmdBuff *cmdBuff
|
||||
backFn actionHandler
|
||||
numSelections int
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func newDetailsView(app *appView, backFn actionHandler) *detailsView {
|
||||
|
|
@ -63,6 +65,9 @@ func (v *detailsView) setCategory(n string) {
|
|||
}
|
||||
|
||||
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||
v.mx.Lock()
|
||||
defer v.mx.Unlock()
|
||||
|
||||
key := evt.Key()
|
||||
if key == tcell.KeyRune {
|
||||
if v.cmdBuff.isActive() {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
|
|
@ -28,6 +29,7 @@ type logsView struct {
|
|||
cancelFunc context.CancelFunc
|
||||
autoScroll bool
|
||||
showPrevious bool
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func newLogsView(pview string, parent loggable) *logsView {
|
||||
|
|
@ -148,6 +150,9 @@ func (v *logsView) load(i int) {
|
|||
}
|
||||
|
||||
func (v *logsView) doLoad(path, co string) error {
|
||||
v.mx.Lock()
|
||||
defer v.mx.Unlock()
|
||||
|
||||
v.stop()
|
||||
|
||||
c := make(chan string)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/tview"
|
||||
|
|
@ -77,7 +78,7 @@ func newKeyAction(d string, a actionHandler, display bool) keyAction {
|
|||
}
|
||||
|
||||
func newMenuView() *menuView {
|
||||
v := menuView{tview.NewTable()}
|
||||
v := menuView{Table: tview.NewTable()}
|
||||
return &v
|
||||
}
|
||||
|
||||
|
|
@ -106,9 +107,14 @@ func (a keyActions) toHints() hints {
|
|||
// -----------------------------------------------------------------------------
|
||||
type menuView struct {
|
||||
*tview.Table
|
||||
|
||||
mx sync.Mutex
|
||||
}
|
||||
|
||||
func (v *menuView) populateMenu(hh hints) {
|
||||
v.mx.Lock()
|
||||
defer v.mx.Unlock()
|
||||
|
||||
v.Clear()
|
||||
sort.Sort(hh)
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ type loggable interface {
|
|||
}
|
||||
|
||||
func newPodView(t string, app *appView, list resource.List) resourceViewer {
|
||||
v := podView{newResourceView(t, app, list).(*resourceView)}
|
||||
v := podView{resourceView: newResourceView(t, app, list).(*resourceView)}
|
||||
{
|
||||
v.extraActionsFn = v.extraActions
|
||||
v.enterFn = v.listContainers
|
||||
|
|
|
|||
|
|
@ -70,11 +70,6 @@ func newResourceView(title string, app *appView, list resource.List) resourceVie
|
|||
details := newDetailsView(app, v.backCmd)
|
||||
v.AddPage("details", details, true, false)
|
||||
|
||||
confirm := tview.NewModal().
|
||||
AddButtons([]string{"Cancel", "OK"}).
|
||||
SetTextColor(tcell.ColorFuchsia)
|
||||
v.AddPage("confirm", confirm, false, false)
|
||||
|
||||
return &v
|
||||
}
|
||||
|
||||
|
|
@ -180,9 +175,7 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
sel := v.getSelectedItem()
|
||||
confirm := v.GetPrimitive("confirm").(*tview.Modal)
|
||||
confirm.SetText(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel))
|
||||
confirm.SetDoneFunc(func(_ int, button string) {
|
||||
v.showModal(fmt.Sprintf("Delete %s %s?", v.list.GetName(), sel), func(_ int, button string) {
|
||||
if button == "OK" {
|
||||
v.getTV().setDeleted()
|
||||
v.app.flash(flashInfo, fmt.Sprintf("Deleting %s %s", v.list.GetName(), sel))
|
||||
|
|
@ -192,13 +185,27 @@ func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
v.refresh()
|
||||
}
|
||||
}
|
||||
v.switchPage(v.list.GetName())
|
||||
v.dismissModal()
|
||||
})
|
||||
v.SwitchToPage("confirm")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *resourceView) showModal(msg string, done func(int, string)) {
|
||||
confirm := tview.NewModal().
|
||||
AddButtons([]string{"Cancel", "OK"}).
|
||||
SetTextColor(tcell.ColorFuchsia).
|
||||
SetText(msg).
|
||||
SetDoneFunc(done)
|
||||
v.AddPage("confirm", confirm, false, false)
|
||||
v.ShowPage("confirm")
|
||||
}
|
||||
|
||||
func (v *resourceView) dismissModal() {
|
||||
v.RemovePage("confirm")
|
||||
v.switchPage(v.list.GetName())
|
||||
}
|
||||
|
||||
func (v *resourceView) defaultEnter(app *appView, ns, resource, selection string) {
|
||||
sel := v.getSelectedItem()
|
||||
yaml, err := v.list.Resource().Describe(v.title, sel, v.app.flags)
|
||||
|
|
@ -270,6 +277,7 @@ func (v *resourceView) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
i, _ := strconv.Atoi(string(evt.Rune()))
|
||||
ns := v.namespaces[i]
|
||||
v.doSwitchNamespace(ns)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -284,6 +292,7 @@ func (v *resourceView) doSwitchNamespace(ns string) {
|
|||
v.list.SetNamespace(v.selectedNS)
|
||||
}
|
||||
v.update.Unlock()
|
||||
|
||||
v.refresh()
|
||||
v.selectItem(0, 0)
|
||||
v.getTV().resetTitle()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
|
|
@ -85,17 +84,14 @@ func (v *replicaSetView) rollbackCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
confirm := v.GetPrimitive("confirm").(*tview.Modal)
|
||||
confirm.SetText(fmt.Sprintf("Rollback %s %s?", v.list.GetName(), v.selectedItem))
|
||||
confirm.SetDoneFunc(func(_ int, button string) {
|
||||
v.showModal(fmt.Sprintf("Rollback %s %s?", v.list.GetName(), v.selectedItem), func(_ int, button string) {
|
||||
if button == "OK" {
|
||||
v.app.flash(flashInfo, fmt.Sprintf("Rolling back %s %s", v.list.GetName(), v.selectedItem))
|
||||
rollback(v.app, v.selectedItem)
|
||||
v.refresh()
|
||||
}
|
||||
v.switchPage(v.list.GetName())
|
||||
v.dismissModal()
|
||||
})
|
||||
v.SwitchToPage("confirm")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package views
|
|||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -111,6 +112,7 @@ func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
if a, ok := v.actions[key]; ok {
|
||||
log.Debug().Msgf(">> TableView handled %s", tcell.KeyNames[key])
|
||||
log.Debug().Msgf("Go Routine %d", runtime.NumGoroutine())
|
||||
return a.action(evt)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue