parent
671f4eab21
commit
81823ae167
|
|
@ -12,7 +12,7 @@ RUN apk --no-cache add make git gcc libc-dev curl && make build
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Build the final Docker image
|
# Build the final Docker image
|
||||||
|
|
||||||
FROM alpine:3.14.2
|
FROM alpine:3.14.3
|
||||||
ARG KUBECTL_VERSION="v1.21.2"
|
ARG KUBECTL_VERSION="v1.21.2"
|
||||||
|
|
||||||
COPY --from=build /k9s/execs/k9s /bin/k9s
|
COPY --from=build /k9s/execs/k9s /bin/k9s
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME)
|
||||||
GIT_REV ?= $(shell git rev-parse --short HEAD)
|
GIT_REV ?= $(shell git rev-parse --short HEAD)
|
||||||
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
SOURCE_DATE_EPOCH ?= $(shell date +%s)
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
VERSION ?= v0.25.1
|
VERSION ?= v0.25.2
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.25.2
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better are as ever very much noted and appreciated!
|
||||||
|
|
||||||
|
If you feel K9s is helping your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||||
|
|
||||||
|
On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM)
|
||||||
|
|
||||||
|
## Maintenance Release!
|
||||||
|
|
||||||
|
Looks like we've broken a few little thingies...
|
||||||
|
May need a few rapid fires to regain some sanity so please bare with us and thank you for your reports!!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Issues
|
||||||
|
|
||||||
|
* [Issue #1311](https://github.com/derailed/k9s/issues/1311) Pressing '?' in logs view (no logs) crashes on nil dereference
|
||||||
|
* [Issue #1310](https://github.com/derailed/k9s/issues/1310) PV/PVC accessMode getting exception
|
||||||
|
* [Issue #1293](https://github.com/derailed/k9s/issues/1293) Broken rollouts for dp/sts/ds with multiple ports of the same number
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
2
go.mod
2
go.mod
|
|
@ -6,7 +6,7 @@ require (
|
||||||
github.com/adrg/xdg v0.3.4
|
github.com/adrg/xdg v0.3.4
|
||||||
github.com/atotto/clipboard v0.1.4
|
github.com/atotto/clipboard v0.1.4
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1
|
github.com/cenkalti/backoff/v4 v4.1.2
|
||||||
github.com/derailed/popeye v0.9.7
|
github.com/derailed/popeye v0.9.7
|
||||||
github.com/derailed/tview v0.6.3
|
github.com/derailed/tview v0.6.3
|
||||||
github.com/fatih/color v1.13.0
|
github.com/fatih/color v1.13.0
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -150,8 +150,8 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ=
|
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,9 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -64,7 +66,12 @@ func (d *Deployment) Scale(ctx context.Context, path string, replicas int32) err
|
||||||
|
|
||||||
// Restart a Deployment rollout.
|
// Restart a Deployment rollout.
|
||||||
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
func (d *Deployment) Restart(ctx context.Context, path string) error {
|
||||||
dp, err := d.Load(d.Factory, path)
|
o, err := d.Factory.Get("apps/v1/deployments", path, true, labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var dp appsv1.Deployment
|
||||||
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &dp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -82,18 +89,27 @@ func (d *Deployment) Restart(ctx context.Context, path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
restarter, err := polymorphichelpers.ObjectRestarterFn(dp)
|
before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), &dp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
after, err := polymorphichelpers.ObjectRestarterFn(&dp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, dp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
_, err = dial.AppsV1().Deployments(dp.Namespace).Patch(
|
_, err = dial.AppsV1().Deployments(dp.Namespace).Patch(
|
||||||
ctx,
|
ctx,
|
||||||
dp.Name,
|
dp.Name,
|
||||||
types.StrategicMergePatchType,
|
types.StrategicMergePatchType,
|
||||||
restarter,
|
diff,
|
||||||
metav1.PatchOptions{},
|
metav1.PatchOptions{},
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,9 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -41,7 +43,12 @@ func (d *DaemonSet) IsHappy(ds appsv1.DaemonSet) bool {
|
||||||
|
|
||||||
// Restart a DaemonSet rollout.
|
// Restart a DaemonSet rollout.
|
||||||
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||||
ds, err := d.GetInstance(path)
|
o, err := d.Factory.Get("apps/v1/daemonsets", path, true, labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ds appsv1.DaemonSet
|
||||||
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -53,12 +60,22 @@ func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||||
if !auth {
|
if !auth {
|
||||||
return fmt.Errorf("user is not authorized to restart a daemonset")
|
return fmt.Errorf("user is not authorized to restart a daemonset")
|
||||||
}
|
}
|
||||||
update, err := polymorphichelpers.ObjectRestarterFn(ds)
|
|
||||||
|
dial, err := d.Client().Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dial, err := d.Client().Dial()
|
before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), &ds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
after, err := polymorphichelpers.ObjectRestarterFn(&ds)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, ds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -66,9 +83,10 @@ func (d *DaemonSet) Restart(ctx context.Context, path string) error {
|
||||||
ctx,
|
ctx,
|
||||||
ds.Name,
|
ds.Name,
|
||||||
types.StrategicMergePatchType,
|
types.StrategicMergePatchType,
|
||||||
update,
|
diff,
|
||||||
metav1.PatchOptions{},
|
metav1.PatchOptions{},
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||||
"k8s.io/kubectl/pkg/polymorphichelpers"
|
"k8s.io/kubectl/pkg/polymorphichelpers"
|
||||||
|
"k8s.io/kubectl/pkg/scheme"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -65,7 +67,12 @@ func (s *StatefulSet) Scale(ctx context.Context, path string, replicas int32) er
|
||||||
|
|
||||||
// Restart a StatefulSet rollout.
|
// Restart a StatefulSet rollout.
|
||||||
func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
||||||
sts, err := s.getStatefulSet(path)
|
o, err := s.Factory.Get("apps/v1/statefulsets", path, true, labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var sts appsv1.StatefulSet
|
||||||
|
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &sts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -75,15 +82,24 @@ func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if !auth {
|
if !auth {
|
||||||
return fmt.Errorf("user is not authorized to update statefulsets")
|
return fmt.Errorf("user is not authorized to restart a statefulset")
|
||||||
}
|
}
|
||||||
|
|
||||||
update, err := polymorphichelpers.ObjectRestarterFn(sts)
|
dial, err := s.Client().Dial()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dial, err := s.Client().Dial()
|
before, err := runtime.Encode(scheme.Codecs.LegacyCodec(appsv1.SchemeGroupVersion), &sts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
after, err := polymorphichelpers.ObjectRestarterFn(&sts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
diff, err := strategicpatch.CreateTwoWayMergePatch(before, after, sts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -91,11 +107,12 @@ func (s *StatefulSet) Restart(ctx context.Context, path string) error {
|
||||||
ctx,
|
ctx,
|
||||||
sts.Name,
|
sts.Name,
|
||||||
types.StrategicMergePatchType,
|
types.StrategicMergePatchType,
|
||||||
update,
|
diff,
|
||||||
metav1.PatchOptions{},
|
metav1.PatchOptions{},
|
||||||
)
|
)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load returns a statefulset instance.
|
// Load returns a statefulset instance.
|
||||||
|
|
|
||||||
|
|
@ -229,9 +229,11 @@ func (l *Log) Stop() {
|
||||||
l.cancelFn()
|
l.cancelFn()
|
||||||
l.cancelFn = nil
|
l.cancelFn = nil
|
||||||
}
|
}
|
||||||
|
if l.logChan != nil {
|
||||||
close(l.logChan)
|
close(l.logChan)
|
||||||
l.logChan = nil
|
l.logChan = nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
l.mx.Unlock()
|
l.mx.Unlock()
|
||||||
l.app.Styles.RemoveListener(l)
|
l.app.Styles.RemoveListener(l)
|
||||||
l.logs.cmdBuff.RemoveListener(l)
|
l.logs.cmdBuff.RemoveListener(l)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
|
@ -30,7 +31,8 @@ func (r *RestartExtender) bindKeys(aa ui.KeyActions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
aa.Add(ui.KeyActions{
|
aa.Add(ui.KeyActions{
|
||||||
tcell.KeyCtrlT: ui.NewKeyAction("Restart", r.restartCmd, true),
|
// BOZO!!
|
||||||
|
ui.KeyR: ui.NewKeyAction("Restart", r.restartCmd, true),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,9 +44,9 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
|
||||||
r.Stop()
|
r.Stop()
|
||||||
defer r.Start()
|
defer r.Start()
|
||||||
msg := fmt.Sprintf("Restart deployment %s?", paths[0])
|
msg := fmt.Sprintf("Restart %s %s?", singularize(r.GVR().R()), paths[0])
|
||||||
if len(paths) > 1 {
|
if len(paths) > 1 {
|
||||||
msg = fmt.Sprintf("Restart %d deployments?", len(paths))
|
msg = fmt.Sprintf("Restart %d %s?", len(paths), r.GVR().R())
|
||||||
}
|
}
|
||||||
dialog.ShowConfirm(r.App().Styles.Dialog(), r.App().Content.Pages, "Confirm Restart", msg, func() {
|
dialog.ShowConfirm(r.App().Styles.Dialog(), r.App().Content.Pages, "Confirm Restart", msg, func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout())
|
ctx, cancel := context.WithTimeout(context.Background(), r.App().Conn().Config().CallTimeout())
|
||||||
|
|
@ -53,7 +55,7 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if err := r.restartRollout(ctx, path); err != nil {
|
if err := r.restartRollout(ctx, path); err != nil {
|
||||||
r.App().Flash().Err(err)
|
r.App().Flash().Err(err)
|
||||||
} else {
|
} else {
|
||||||
r.App().Flash().Infof("Rollout restart in progress for `%s...", path)
|
r.App().Flash().Infof("Restart in progress for `%s...", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, func() {})
|
}, func() {})
|
||||||
|
|
@ -73,3 +75,13 @@ func (r *RestartExtender) restartRollout(ctx context.Context, path string) error
|
||||||
|
|
||||||
return s.Restart(ctx, path)
|
return s.Restart(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
func singularize(s string) string {
|
||||||
|
if strings.LastIndex(s, "s") == len(s)-1 {
|
||||||
|
return s[:len(s)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func (s *ScaleExtender) showScaleDialog(paths []string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
confirm := tview.NewModalForm("<Scale>", form)
|
confirm := tview.NewModalForm("<Scale>", form)
|
||||||
msg := fmt.Sprintf("Scale %s %s?", s.GVR().R(), paths[0])
|
msg := fmt.Sprintf("Scale %s %s?", singularize(s.GVR().R()), paths[0])
|
||||||
if len(paths) > 1 {
|
if len(paths) > 1 {
|
||||||
msg = fmt.Sprintf("Scale [%d] %s?", len(paths), s.GVR().R())
|
msg = fmt.Sprintf("Scale [%d] %s?", len(paths), s.GVR().R())
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +114,7 @@ func (s *ScaleExtender) makeScaleForm(sels []string) (*tview.Form, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(sels) == 1 {
|
if len(sels) == 1 {
|
||||||
s.App().Flash().Infof("[%d] %s scaled successfully", len(sels), s.GVR().R())
|
s.App().Flash().Infof("[%d] %s scaled successfully", len(sels), singularize(s.GVR().R()))
|
||||||
} else {
|
} else {
|
||||||
s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), sels[0])
|
s.App().Flash().Infof("%s %s scaled successfully", s.GVR().R(), sels[0])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue