Rel v0.40.7 (#3188)
* [FEAT] add readonly indicator * [FIX] Hosed cust view loading * rel notesmine
parent
1244cc518d
commit
08b8efa617
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
|||
else
|
||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||
endif
|
||||
VERSION ?= v0.40.6
|
||||
VERSION ?= v0.40.7
|
||||
IMG_NAME := derailed/k9s
|
||||
IMAGE := ${IMG_NAME}:${VERSION}
|
||||
|
||||
|
|
|
|||
|
|
@ -700,6 +700,12 @@ views:
|
|||
- AGE
|
||||
- NAMESPACE|WR
|
||||
|
||||
v1/pods@kube*: # => 🌚 New v0.40.6! You can also specify a namespace using a regular expression.
|
||||
columns:
|
||||
- AGE
|
||||
- NAMESPACE|WR
|
||||
|
||||
|
||||
v1/services:
|
||||
columns:
|
||||
- AGE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
|
||||
|
||||
# Release v0.40.7
|
||||
|
||||
## 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!
|
||||
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!
|
||||
|
||||
As you may know, K9s is not pimped out by corps with deep pockets, thus 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!
|
||||
|
||||
🙀 Hoy! Hosed custom view loading in v0.40.6...
|
||||
|
||||
## Videos Are In The Can!
|
||||
|
||||
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
|
||||
|
||||
* [K9s v0.40.0 -Column Blow- Sneak peek](https://youtu.be/iy6RDozAM4A)
|
||||
* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE)
|
||||
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
|
||||
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
|
||||
|
||||
---
|
||||
|
||||
## Resolved Issues
|
||||
|
||||
---
|
||||
|
||||
## Contributed PRs
|
||||
|
||||
Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!
|
||||
|
||||
* [#3186](https://github.com/derailed/k9s/pull/3186) fix: allow absolute paths for the 'dir' command
|
||||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
|
@ -34,6 +34,11 @@ func NewConfig(ks data.KubeSettings) *Config {
|
|||
}
|
||||
}
|
||||
|
||||
// IsReadOnly returns true if K9s is running in read-only mode.
|
||||
func (c *Config) IsReadOnly() bool {
|
||||
return c.K9s.IsReadOnly()
|
||||
}
|
||||
|
||||
// ActiveClusterName returns the corresponding cluster name.
|
||||
func (c *Config) ActiveClusterName(contextName string) (string, error) {
|
||||
ct, err := c.settings.GetContext(contextName)
|
||||
|
|
|
|||
|
|
@ -142,28 +142,33 @@ func (v *CustomView) fireConfigChanged() {
|
|||
|
||||
func (v *CustomView) getVS(gvr, ns string) *ViewSetting {
|
||||
k := gvr
|
||||
if ns != "" {
|
||||
k += "@" + ns
|
||||
}
|
||||
|
||||
for key := range maps.Keys(v.Views) {
|
||||
kk := slices.Collect(maps.Keys(v.Views))
|
||||
slices.SortFunc(kk, func(s1, s2 string) int {
|
||||
return strings.Compare(s1, s2)
|
||||
})
|
||||
slices.Reverse(kk)
|
||||
for _, key := range kk {
|
||||
if !strings.HasPrefix(key, gvr) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case key == k:
|
||||
vs := v.Views[key]
|
||||
return &vs
|
||||
case strings.Contains(key, "@"):
|
||||
tt := strings.Split(key, "@")
|
||||
if len(tt) != 2 {
|
||||
break
|
||||
}
|
||||
if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(k) {
|
||||
nsk := gvr
|
||||
if ns != "" {
|
||||
nsk += "@" + ns
|
||||
}
|
||||
if rx, err := regexp.Compile(tt[1]); err == nil && rx.MatchString(nsk) {
|
||||
vs := v.Views[key]
|
||||
return &vs
|
||||
}
|
||||
case key == k:
|
||||
vs := v.Views[key]
|
||||
return &vs
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ func TestCustomView_getVS(t *testing.T) {
|
|||
"toast-no-ns": {
|
||||
gvr: "v1/pods",
|
||||
ns: "zorg",
|
||||
e: &ViewSetting{
|
||||
Columns: []string{"NAMESPACE", "NAME", "AGE", "IP"},
|
||||
},
|
||||
},
|
||||
|
||||
"toast-no-res": {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"github.com/derailed/k9s/internal"
|
||||
"github.com/derailed/k9s/internal/client"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/dao"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/model1"
|
||||
"github.com/derailed/k9s/internal/render"
|
||||
|
|
@ -54,6 +53,7 @@ type Table struct {
|
|||
hasMetrics bool
|
||||
ctx context.Context
|
||||
mx sync.RWMutex
|
||||
readOnly bool
|
||||
}
|
||||
|
||||
// NewTable returns a new table view.
|
||||
|
|
@ -72,6 +72,13 @@ func NewTable(gvr client.GVR) *Table {
|
|||
}
|
||||
}
|
||||
|
||||
func (t *Table) SetReadOnly(ro bool) {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
||||
t.readOnly = ro
|
||||
}
|
||||
|
||||
func (t *Table) setSortCol(sc model1.SortColumn) {
|
||||
t.mx.Lock()
|
||||
defer t.mx.Unlock()
|
||||
|
|
@ -297,17 +304,12 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
|||
fg := t.styles.Table().Header.FgColor.Color()
|
||||
bg := t.styles.Table().Header.BgColor.Color()
|
||||
|
||||
var isNamespaced bool
|
||||
if m, err := dao.MetaAccess.MetaFor(t.GVR()); err == nil {
|
||||
isNamespaced = m.Namespaced
|
||||
}
|
||||
|
||||
var col int
|
||||
for _, h := range cdata.Header() {
|
||||
if h.Hide || (!t.wide && h.Wide) {
|
||||
continue
|
||||
}
|
||||
if h.Name == "NAMESPACE" && (!t.GetModel().ClusterWide() || !isNamespaced) {
|
||||
if h.Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
||||
continue
|
||||
}
|
||||
if h.MX && !t.hasMetrics {
|
||||
|
|
@ -333,7 +335,7 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
|||
slog.Error("Unable to find original row event", slogs.RowID, re.Row.ID)
|
||||
return true
|
||||
}
|
||||
t.buildRow(row+1, re, ore, cdata.Header(), pads, isNamespaced)
|
||||
t.buildRow(row+1, re, ore, cdata.Header(), pads)
|
||||
|
||||
return true
|
||||
})
|
||||
|
|
@ -342,7 +344,7 @@ func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
|||
t.UpdateTitle()
|
||||
}
|
||||
|
||||
func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads MaxyPad, isNamespaced bool) {
|
||||
func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads MaxyPad) {
|
||||
color := model1.DefaultColorer
|
||||
if t.colorerFn != nil {
|
||||
color = t.colorerFn
|
||||
|
|
@ -364,7 +366,7 @@ func (t *Table) buildRow(r int, re, ore model1.RowEvent, h model1.Header, pads M
|
|||
continue
|
||||
}
|
||||
|
||||
if h[c].Name == "NAMESPACE" && (!t.GetModel().ClusterWide() || !isNamespaced) {
|
||||
if h[c].Name == "NAMESPACE" && !t.GetModel().ClusterWide() {
|
||||
continue
|
||||
}
|
||||
if h[c].MX && !t.hasMetrics {
|
||||
|
|
@ -532,11 +534,12 @@ func (t *Table) styleTitle() string {
|
|||
if t.Extras != "" {
|
||||
ns = t.Extras
|
||||
}
|
||||
|
||||
var title string
|
||||
if ns == client.ClusterScope {
|
||||
title = SkinTitle(fmt.Sprintf(TitleFmt, t.gvr, render.AsThousands(rc)), t.styles.Frame())
|
||||
title = SkinTitle(fmt.Sprintf(TitleFmt, ROIndicator(t.readOnly), t.gvr, render.AsThousands(rc)), t.styles.Frame())
|
||||
} else {
|
||||
title = SkinTitle(fmt.Sprintf(NSTitleFmt, t.gvr, ns, render.AsThousands(rc)), t.styles.Frame())
|
||||
title = SkinTitle(fmt.Sprintf(NSTitleFmt, ROIndicator(t.readOnly), t.gvr, ns, render.AsThousands(rc)), t.styles.Frame())
|
||||
}
|
||||
|
||||
buff := t.cmdBuff.GetText()
|
||||
|
|
@ -552,3 +555,12 @@ func (t *Table) styleTitle() string {
|
|||
|
||||
return title + SkinTitle(fmt.Sprintf(SearchFmt, buff), t.styles.Frame())
|
||||
}
|
||||
|
||||
// ROIndicator returns an icon showing whether the session is in readonly mode or not.
|
||||
func ROIndicator(ro bool) string {
|
||||
if ro {
|
||||
return LockedIC
|
||||
}
|
||||
|
||||
return UnlockedIC
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,10 +23,10 @@ const (
|
|||
SearchFmt = "<[filter:bg:r]/%s[fg:bg:-]> "
|
||||
|
||||
// NSTitleFmt represents a namespaced view title.
|
||||
NSTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
|
||||
NSTitleFmt = " %s [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
|
||||
|
||||
// TitleFmt represents a standard view title.
|
||||
TitleFmt = "[fg:bg:b] %s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
|
||||
TitleFmt = " %s [fg:bg:b]%s[fg:bg:-][[count:bg:b]%s[fg:bg:-]][fg:bg:-] "
|
||||
|
||||
descIndicator = "↓"
|
||||
ascIndicator = "↑"
|
||||
|
|
|
|||
|
|
@ -15,6 +15,14 @@ import (
|
|||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnlockedIC represents an unlocked icon.
|
||||
UnlockedIC = "🔓"
|
||||
|
||||
// LockedIC represents a locked icon.
|
||||
LockedIC = "🔒"
|
||||
)
|
||||
|
||||
// Namespaceable represents a namespaceable model.
|
||||
type Namespaceable interface {
|
||||
// ClusterWide returns true if the model represents resource in all namespaces.
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
|
|||
var (
|
||||
errs error
|
||||
aliases = r.Aliases()
|
||||
ro = r.App().Config.K9s.IsReadOnly()
|
||||
ro = r.App().Config.IsReadOnly()
|
||||
)
|
||||
for k, plugin := range pp.Plugins {
|
||||
if !inScope(plugin.Scopes, aliases) {
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ func (b *Browser) Init(ctx context.Context) error {
|
|||
if b.App().IsRunning() {
|
||||
b.app.CmdBuff().Reset()
|
||||
}
|
||||
b.Table.SetReadOnly(b.app.Config.IsReadOnly())
|
||||
|
||||
b.bindKeys(b.Actions())
|
||||
for _, f := range b.bindKeysFn {
|
||||
|
|
@ -537,7 +538,7 @@ func (b *Browser) refreshActions() {
|
|||
|
||||
if b.app.ConOK() {
|
||||
b.namespaceActions(aa)
|
||||
if !b.app.Config.K9s.IsReadOnly() {
|
||||
if !b.app.Config.IsReadOnly() {
|
||||
if client.Can(b.meta.Verbs, "edit") {
|
||||
aa.Add(ui.KeyE, ui.NewKeyActionWithOpts("Edit", b.editCmd,
|
||||
ui.ActionOpts{
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ func (c *Container) bindDangerousKeys(aa *ui.KeyActions) {
|
|||
func (c *Container) bindKeys(aa *ui.KeyActions) {
|
||||
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
|
||||
if !c.App().Config.K9s.IsReadOnly() {
|
||||
if !c.App().Config.IsReadOnly() {
|
||||
c.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ func (d *Dir) bindKeys(aa *ui.KeyActions) {
|
|||
// !!BOZO!! Lame!
|
||||
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
|
||||
aa.Delete(tcell.KeyCtrlW, tcell.KeyCtrlL, tcell.KeyCtrlD, tcell.KeyCtrlZ)
|
||||
if !d.App().Config.K9s.IsReadOnly() {
|
||||
if !d.App().Config.IsReadOnly() {
|
||||
d.bindDangerousKeys(aa)
|
||||
}
|
||||
aa.Bulk(ui.KeyMap{
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func (h *History) HistoryContext(ctx context.Context) context.Context {
|
|||
}
|
||||
|
||||
func (h *History) bindKeys(aa *ui.KeyActions) {
|
||||
if !h.App().Config.K9s.IsReadOnly() {
|
||||
if !h.App().Config.IsReadOnly() {
|
||||
h.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ func NewImageExtender(r ResourceViewer) ResourceViewer {
|
|||
}
|
||||
|
||||
func (s *ImageExtender) bindKeys(aa *ui.KeyActions) {
|
||||
if s.App().Config.K9s.IsReadOnly() {
|
||||
if s.App().Config.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
aa.Add(ui.KeyI, ui.NewKeyAction("Set Image", s.setImageCmd, false))
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ func (v *LiveView) bindKeys() {
|
|||
tcell.KeyDelete: ui.NewSharedKeyAction("Erase", v.eraseCmd, false),
|
||||
})
|
||||
|
||||
if !v.app.Config.K9s.IsReadOnly() {
|
||||
if !v.app.Config.IsReadOnly() {
|
||||
v.actions.Add(ui.KeyE, ui.NewKeyAction("Edit", v.editCmd, true))
|
||||
}
|
||||
if v.title == yamlAction {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func (n *Node) bindDangerousKeys(aa *ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (n *Node) bindKeys(aa *ui.KeyActions) {
|
||||
if !n.App().Config.K9s.IsReadOnly() {
|
||||
if !n.App().Config.IsReadOnly() {
|
||||
n.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ func (p *Pod) bindDangerousKeys(aa *ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (p *Pod) bindKeys(aa *ui.KeyActions) {
|
||||
if !p.App().Config.K9s.IsReadOnly() {
|
||||
if !p.App().Config.IsReadOnly() {
|
||||
p.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ func NewRestartExtender(v ResourceViewer) ResourceViewer {
|
|||
|
||||
// BindKeys creates additional menu actions.
|
||||
func (r *RestartExtender) bindKeys(aa *ui.KeyActions) {
|
||||
if r.App().Config.K9s.IsReadOnly() {
|
||||
if r.App().Config.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
aa.Add(ui.KeyR, ui.NewKeyActionWithOpts("Restart", r.restartCmd,
|
||||
|
|
|
|||
|
|
@ -419,9 +419,9 @@ func (s *Sanitizer) styleTitle() string {
|
|||
|
||||
var title string
|
||||
if ns == client.ClusterScope {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
|
||||
} else {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, ui.ROIndicator(s.app.Config.IsReadOnly()), base, ns, render.AsThousands(int64(s.Count))), s.app.Styles.Frame())
|
||||
}
|
||||
|
||||
buff := s.CmdBuff().GetText()
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func NewScaleExtender(r ResourceViewer) ResourceViewer {
|
|||
}
|
||||
|
||||
func (s *ScaleExtender) bindKeys(aa *ui.KeyActions) {
|
||||
if s.App().Config.K9s.IsReadOnly() {
|
||||
if s.App().Config.IsReadOnly() {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ func (w *Workload) bindDangerousKeys(aa *ui.KeyActions) {
|
|||
}
|
||||
|
||||
func (w *Workload) bindKeys(aa *ui.KeyActions) {
|
||||
if !w.App().Config.K9s.IsReadOnly() {
|
||||
if !w.App().Config.IsReadOnly() {
|
||||
w.bindDangerousKeys(aa)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -671,9 +671,9 @@ func (x *Xray) styleTitle() string {
|
|||
|
||||
var title string
|
||||
if ns == client.ClusterScope {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, ui.ROIndicator(x.app.Config.IsReadOnly()), base, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
|
||||
} else {
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
|
||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, ui.ROIndicator(x.app.Config.IsReadOnly()), base, ns, render.AsThousands(int64(x.Count))), x.app.Styles.Frame())
|
||||
}
|
||||
|
||||
buff := x.CmdBuff().GetText()
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
name: k9s
|
||||
base: core22
|
||||
version: 'v0.40.6'
|
||||
version: 'v0.40.7'
|
||||
summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
||||
description: |
|
||||
K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session.
|
||||
|
|
|
|||
Loading…
Reference in New Issue