checkpoint
parent
05558c89fa
commit
2c57888bac
|
|
@ -0,0 +1,301 @@
|
|||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 1m
|
||||
|
||||
# exit code when at least one issue was found, default is 1
|
||||
issues-exit-code: 1
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# list of build tags, all linters use it. Default is empty list.
|
||||
# build-tags:
|
||||
# - mytag
|
||||
|
||||
# which dirs to skip: issues from them won't be reported;
|
||||
# can use regexp here: generated.*, regexp is applied on full path;
|
||||
# default value is empty list, but default dirs are skipped independently
|
||||
# from this option's value (see skip-dirs-use-default).
|
||||
# skip-dirs:
|
||||
# - src/external_libs
|
||||
# - autogenerated_by_my_lib
|
||||
|
||||
# default is true. Enables skipping of directories:
|
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
|
||||
skip-dirs-use-default: true
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
# skip-files:
|
||||
# - ".*\\.my\\.go$"
|
||||
# - lib/bad.go
|
||||
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules":
|
||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit
|
||||
# automatic updating of go.mod described above. Instead, it fails when any changes
|
||||
# to go.mod are needed. This setting is most useful to check that go.mod does
|
||||
# not need updates, such as in a continuous integration and testing system.
|
||||
# If invoked with -mod=vendor, the go command assumes that the vendor
|
||||
# directory holds the correct copies of dependencies and ignores
|
||||
# the dependency descriptions in go.mod.
|
||||
# modules-download-mode: readonly|release|vendor
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
format: colored-line-number
|
||||
|
||||
# print lines of code with issue, default is true
|
||||
print-issued-lines: true
|
||||
|
||||
# print linter name in the end of issue text, default is true
|
||||
print-linter-name: true
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-type-assertions: true
|
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||
# default is false: such cases aren't reported by default.
|
||||
check-blank: false
|
||||
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
# ignore: fmt:.*,io/ioutil:^Read.*
|
||||
# path to a file containing a list of functions to exclude from checking
|
||||
# see https://github.com/kisielk/errcheck#excluding-functions for details
|
||||
# exclude: /path/to/file.txt
|
||||
|
||||
funlen:
|
||||
lines: 60
|
||||
statements: 40
|
||||
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: true
|
||||
|
||||
# settings per analyzer
|
||||
settings:
|
||||
printf: # analyzer name, run `go tool vet help` to see all analyzers
|
||||
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf
|
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf
|
||||
|
||||
# enable or disable analyzers by name
|
||||
enable:
|
||||
- atomicalign
|
||||
enable-all: false
|
||||
disable:
|
||||
- shadow
|
||||
disable-all: false
|
||||
golint:
|
||||
# minimal confidence for issues, default is 0.8
|
||||
min-confidence: 0.8
|
||||
gofmt:
|
||||
# simplify code: gofmt with `-s` option, true by default
|
||||
simplify: true
|
||||
goimports:
|
||||
# put imports beginning with prefix after 3rd-party packages;
|
||||
# it's a comma-separated list of prefixes
|
||||
local-prefixes: github.com/org/project
|
||||
gocyclo:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 10
|
||||
gocognit:
|
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20)
|
||||
min-complexity: 10
|
||||
maligned:
|
||||
# print struct with more effective memory layout or not, false by default
|
||||
suggest-new: true
|
||||
dupl:
|
||||
# tokens count to trigger issue, 150 by default
|
||||
threshold: 100
|
||||
goconst:
|
||||
# minimal length of string constant, 3 by default
|
||||
min-len: 3
|
||||
# minimal occurrences count to trigger, 3 by default
|
||||
min-occurrences: 3
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
include-go-root: false
|
||||
packages:
|
||||
- github.com/sirupsen/logrus
|
||||
packages-with-error-messages:
|
||||
# specify an error message to output when a blacklisted package is used
|
||||
github.com/sirupsen/logrus: "logging is allowed only by logutils.Log"
|
||||
misspell:
|
||||
# Correct spellings using locale preferences for US or UK.
|
||||
# Default is to use a neutral variety of English.
|
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'.
|
||||
locale: US
|
||||
ignore-words:
|
||||
- someword
|
||||
lll:
|
||||
# max line length, lines longer will be reported. Default is 120.
|
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option
|
||||
line-length: 120
|
||||
# tab width in spaces. Default to 1.
|
||||
tab-width: 1
|
||||
unused:
|
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false.
|
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
unparam:
|
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
|
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
|
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
|
||||
# with golangci-lint call it on a directory with the changed file.
|
||||
check-exported: false
|
||||
nakedret:
|
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
|
||||
max-func-lines: 30
|
||||
prealloc:
|
||||
# XXX: we don't recommend using this linter before doing performance profiling.
|
||||
# For most programs usage of prealloc will be a premature optimization.
|
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
|
||||
# True by default.
|
||||
simple: true
|
||||
range-loops: true # Report preallocation suggestions on range loops, true by default
|
||||
for-loops: false # Report preallocation suggestions on for loops, false by default
|
||||
gocritic:
|
||||
# Which checks should be enabled; can't be combined with 'disabled-checks';
|
||||
# See https://go-critic.github.io/overview#checks-overview
|
||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
|
||||
# By default list of stable checks is used.
|
||||
enabled-checks:
|
||||
- rangeValCopy
|
||||
|
||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
|
||||
disabled-checks:
|
||||
- regexpMust
|
||||
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- performance
|
||||
|
||||
settings: # settings passed to gocritic
|
||||
captLocal: # must be valid enabled check name
|
||||
paramsOnly: true
|
||||
rangeValCopy:
|
||||
sizeThreshold: 32
|
||||
godox:
|
||||
# report any comments starting with keywords, this is useful for TODO or FIXME comments that
|
||||
# might be left in the code accidentally and should be resolved before merging
|
||||
keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting
|
||||
- NOTE
|
||||
- OPTIMIZE # marks code that should be optimized before merging
|
||||
- HACK # marks hack-arounds that should be removed before merging
|
||||
dogsled:
|
||||
# checks assignments with too many blank identifiers; default is 2
|
||||
max-blank-identifiers: 2
|
||||
|
||||
whitespace:
|
||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||
wsl:
|
||||
# If true append is only allowed to be cuddled if appending value is
|
||||
# matching variables, fields or types on line above. Default is true.
|
||||
strict-append: true
|
||||
# Allow calls and assignments to be cuddled as long as the lines have any
|
||||
# matching variables, fields or types. Default is true.
|
||||
allow-assign-and-call: true
|
||||
# Allow multiline assignments to be cuddled. Default is true.
|
||||
allow-multiline-assign: true
|
||||
# Allow declarations (var) to be cuddled.
|
||||
allow-cuddle-declarations: false
|
||||
# Allow trailing comments in ending of blocks
|
||||
allow-trailing-comment: false
|
||||
# Force newlines in end of case at this limit (0 = never).
|
||||
force-case-trailing-whitespace: 0
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
disable:
|
||||
- maligned
|
||||
- prealloc
|
||||
disable-all: false
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
- abcdef
|
||||
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
|
||||
# Exclude known linters from partially hard-vendored code,
|
||||
# which is impossible to exclude via "nolint" comments.
|
||||
- path: internal/hmac/
|
||||
text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
|
||||
# Exclude some staticcheck messages
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
|
||||
# Exclude lll issues for long lines with go:generate
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
|
||||
# Independently from option `exclude` we use default exclude patterns,
|
||||
# it can be disabled by this option. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`.
|
||||
# Default value for this option is true.
|
||||
exclude-use-default: false
|
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
|
||||
max-issues-per-linter: 0
|
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
|
||||
max-same-issues: 0
|
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files,
|
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
|
||||
# It's a super-useful option for integration of golangci-lint into existing
|
||||
# large codebase. It's not practical to fix all existing issues at the moment
|
||||
# of integration: much better don't allow issues in new code.
|
||||
# Default is false.
|
||||
new: false
|
||||
|
||||
# Show only new issues created after git revision `REV`
|
||||
new-from-rev: REV
|
||||
|
||||
# Show only new issues created in git patch with set file path.
|
||||
new-from-patch: path/to/patch/file
|
||||
|
|
@ -22,7 +22,6 @@ This is a maintenance release to mainly resolve outstanding issues and bugs.
|
|||
|
||||
Added two new columns to track cpu/memory utilization on metrics-server enabled clusters. These apply to pod,container and node view.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Resolved Bugs
|
||||
|
|
|
|||
|
|
@ -12,6 +12,11 @@ type MenuHint struct {
|
|||
Visible bool
|
||||
}
|
||||
|
||||
// IsBlank checks if menu hint is a place holder.
|
||||
func (m MenuHint) IsBlank() bool {
|
||||
return m.Mnemonic == "" && m.Description == "" && m.Visible == false
|
||||
}
|
||||
|
||||
// MenuHints represents a collection of hints.
|
||||
type MenuHints []MenuHint
|
||||
|
||||
|
|
|
|||
|
|
@ -74,14 +74,9 @@ func (s *Stack) RemoveListener(l StackListener) {
|
|||
// AddListener registers a stack listener.
|
||||
func (s *Stack) AddListener(l StackListener) {
|
||||
s.listeners = append(s.listeners, l)
|
||||
log.Debug().Msgf("Stack Add listener %#v", s.components)
|
||||
s.DumpStack()
|
||||
if s.Empty() {
|
||||
log.Debug().Msgf("Stack is empty!")
|
||||
} else {
|
||||
log.Debug().Msgf("TOP is %s", s.Top().Name())
|
||||
if !s.Empty() {
|
||||
l.StackTop(s.Top())
|
||||
}
|
||||
l.StackTop(s.Top())
|
||||
}
|
||||
|
||||
// Dump prints out the stack.
|
||||
|
|
@ -114,7 +109,7 @@ func (s *Stack) Pop() (Component, bool) {
|
|||
c.Stop()
|
||||
|
||||
if top := s.Top(); top != nil {
|
||||
log.Debug().Msgf("Calling Restart on %s", top.Name())
|
||||
log.Debug().Msgf("Calling Start on %s", top.Name())
|
||||
top.Start()
|
||||
}
|
||||
|
||||
|
|
@ -131,6 +126,15 @@ func (s *Stack) IsLast() bool {
|
|||
return len(s.components) == 1
|
||||
}
|
||||
|
||||
// Previous returns the previous component if any.
|
||||
func (s *Stack) Previous() Component {
|
||||
if s.IsLast() {
|
||||
return s.Top()
|
||||
}
|
||||
|
||||
return s.components[len(s.components)-2]
|
||||
}
|
||||
|
||||
// Top returns the top most item or nil if the stack is empty.
|
||||
func (s *Stack) Top() Component {
|
||||
if s.Empty() {
|
||||
|
|
|
|||
|
|
@ -64,11 +64,6 @@ func (r *Container) Marshal(path string) (string, error) {
|
|||
return "", nil
|
||||
}
|
||||
|
||||
// // PodLogs tail logs for all containers in a running Pod.
|
||||
// func (r *Container) PodLogs(ctx context.Context, c chan<- string, ns, n string, lines int64, prev bool) error {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// Logs tails a given container logs
|
||||
func (r *Container) Logs(ctx context.Context, c chan<- string, opts LogOptions) error {
|
||||
res, ok := r.Resource.(k8s.Loggable)
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ type (
|
|||
func NewPodList(c Connection, ns string) List {
|
||||
return NewList(
|
||||
ns,
|
||||
"po",
|
||||
"pods",
|
||||
NewPod(c),
|
||||
AllVerbsAccess|DescribeAccess,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package ui
|
|||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/k8s"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
|
@ -13,7 +12,6 @@ type App struct {
|
|||
Configurator
|
||||
|
||||
Main *Pages
|
||||
Hint *model.Hint
|
||||
|
||||
actions KeyActions
|
||||
|
||||
|
|
@ -28,7 +26,6 @@ func NewApp() *App {
|
|||
actions: make(KeyActions),
|
||||
Main: NewPages(),
|
||||
cmdBuff: NewCmdBuff(':', CommandBuff),
|
||||
Hint: model.NewHint(),
|
||||
}
|
||||
|
||||
a.RefreshStyles()
|
||||
|
|
@ -50,8 +47,6 @@ func (a *App) Init() {
|
|||
a.SetInputCapture(a.keyboard)
|
||||
a.cmdBuff.AddListener(a.Cmd())
|
||||
a.SetRoot(a.Main, true)
|
||||
|
||||
a.Hint.AddListener(a.Menu())
|
||||
}
|
||||
|
||||
// Conn returns an api server connection.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Crumbs represents user breadcrumbs.
|
||||
|
|
@ -35,14 +34,12 @@ func NewCrumbs(styles *config.Styles) *Crumbs {
|
|||
// StackPushed indicates a new item was added.
|
||||
func (v *Crumbs) StackPushed(c model.Component) {
|
||||
v.stack.Push(c)
|
||||
log.Debug().Msgf(">>> PUSH %v", v.stack.Flatten())
|
||||
v.refresh(v.stack.Flatten())
|
||||
}
|
||||
|
||||
// StackPopped indicates an item was deleted
|
||||
func (v *Crumbs) StackPopped(_, _ model.Component) {
|
||||
v.stack.Pop()
|
||||
log.Debug().Msgf("<<< POP %v", v.stack.Flatten())
|
||||
v.refresh(v.stack.Flatten())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
const (
|
||||
menuIndexFmt = " [key:bg:b]<%d> [fg:bg:d]%s "
|
||||
maxRows = 7
|
||||
chopWidth = 20
|
||||
)
|
||||
|
||||
var menuRX = regexp.MustCompile(`\d`)
|
||||
|
|
@ -36,9 +37,16 @@ func NewMenu(styles *config.Styles) *Menu {
|
|||
return &v
|
||||
}
|
||||
|
||||
// HintsChanged updates the menu based on hints changing.
|
||||
func (v *Menu) HintsChanged(hh model.MenuHints) {
|
||||
v.HydrateMenu(hh)
|
||||
func (v *Menu) StackPushed(c model.Component) {
|
||||
v.HydrateMenu(c.Hints())
|
||||
}
|
||||
|
||||
func (v *Menu) StackPopped(o, n model.Component) {
|
||||
v.HydrateMenu(n.Hints())
|
||||
}
|
||||
|
||||
func (v *Menu) StackTop(t model.Component) {
|
||||
v.HydrateMenu(t.Hints())
|
||||
}
|
||||
|
||||
// HydrateMenu populate menu ui from hints.
|
||||
|
|
@ -58,17 +66,34 @@ func (v *Menu) HydrateMenu(hh model.MenuHints) {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *Menu) hasDigits(hh model.MenuHints) bool {
|
||||
for _, h := range hh {
|
||||
if !h.Visible {
|
||||
continue
|
||||
}
|
||||
if menuRX.MatchString(h.Mnemonic) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
|
||||
table := make([]model.MenuHints, maxRows+1)
|
||||
|
||||
colCount := (len(hh) / maxRows) + 1
|
||||
|
||||
if v.hasDigits(hh) {
|
||||
colCount++
|
||||
}
|
||||
|
||||
for row := 0; row < maxRows; row++ {
|
||||
table[row] = make(model.MenuHints, colCount+1)
|
||||
table[row] = make(model.MenuHints, colCount)
|
||||
}
|
||||
|
||||
var row, col int
|
||||
firstCmd := true
|
||||
maxKeys := make([]int, colCount+1)
|
||||
maxKeys := make([]int, colCount)
|
||||
for _, h := range hh {
|
||||
if !h.Visible {
|
||||
continue
|
||||
|
|
@ -76,6 +101,9 @@ func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
|
|||
isDigit := menuRX.MatchString(h.Mnemonic)
|
||||
if !isDigit && firstCmd {
|
||||
row, col, firstCmd = 0, col+1, false
|
||||
if table[0][0].IsBlank() {
|
||||
col = 0
|
||||
}
|
||||
}
|
||||
if maxKeys[col] < len(h.Mnemonic) {
|
||||
maxKeys[col] = len(h.Mnemonic)
|
||||
|
|
@ -142,7 +170,7 @@ func formatNSMenu(i int, name string, styles config.Frame) string {
|
|||
fmat := strings.Replace(menuIndexFmt, "[key", "["+styles.Menu.NumKeyColor, 1)
|
||||
fmat = strings.Replace(fmat, ":bg:", ":"+styles.Title.BgColor+":", -1)
|
||||
fmat = strings.Replace(fmat, "[fg", "["+styles.Menu.FgColor, 1)
|
||||
return fmt.Sprintf(fmat, i, Truncate(name, 14))
|
||||
return fmt.Sprintf(fmat, i, Truncate(name, chopWidth))
|
||||
}
|
||||
|
||||
func formatPlainMenu(h model.MenuHint, size int, styles config.Frame) string {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ func NewTable(title string) *Table {
|
|||
}
|
||||
|
||||
func (t *Table) Init(ctx context.Context) {
|
||||
log.Debug().Msgf("UI Table INIT %q", t.baseTitle)
|
||||
t.styles = ctx.Value(KeyStyles).(*config.Styles)
|
||||
|
||||
t.SetFixed(1, 0)
|
||||
|
|
@ -78,7 +79,6 @@ func (t *Table) Init(ctx context.Context) {
|
|||
|
||||
t.SetSelectionChangedFunc(t.selChanged)
|
||||
t.SetInputCapture(t.keyboard)
|
||||
|
||||
}
|
||||
|
||||
// SendKey sends an keyboard event (testing only!).
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func (a *Alias) registerActions() {
|
|||
a.RmAction(tcell.KeyCtrlS)
|
||||
|
||||
a.AddActions(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto Resource", a.gotoCmd, true),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false),
|
||||
ui.KeySlash: ui.NewKeyAction("Filter", a.activateCmd, false),
|
||||
ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.SortColCmd(0), false),
|
||||
|
|
@ -82,6 +82,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
if r != 0 {
|
||||
s := ui.TrimCell(a.Table.Table, r, 1)
|
||||
tokens := strings.Split(s, ",")
|
||||
a.app.Content.Pop()
|
||||
a.app.gotoResource(tokens[0], true)
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import (
|
|||
|
||||
const (
|
||||
splashTime = 1
|
||||
devMode = "dev"
|
||||
clusterRefresh = time.Duration(5 * time.Second)
|
||||
indicatorFmt = "[orange::b]K9s [aqua::]%s [white::]%s:%s:%s [lawngreen::]%s%%[white::]::[darkturquoise::]%s%%"
|
||||
)
|
||||
|
|
@ -27,8 +26,6 @@ const (
|
|||
// ActionsFunc augments Keybindinga.
|
||||
type ActionsFunc func(ui.KeyActions)
|
||||
|
||||
type focusHandler func(tview.Primitive)
|
||||
|
||||
type forwarder interface {
|
||||
Start(path, co string, ports []string) (*portforward.PortForwarder, error)
|
||||
Stop()
|
||||
|
|
@ -86,7 +83,9 @@ func (a *App) ActiveView() model.Component {
|
|||
}
|
||||
|
||||
func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
a.Content.Pop()
|
||||
if !a.Content.IsLast() {
|
||||
a.Content.Pop()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -95,6 +94,7 @@ func (a *App) Init(version string, rate int) {
|
|||
ctx := context.WithValue(context.Background(), ui.KeyApp, a)
|
||||
a.Content.Init(ctx)
|
||||
a.Content.Stack.AddListener(a.Crumbs())
|
||||
a.Content.Stack.AddListener(a.Menu())
|
||||
|
||||
a.version = version
|
||||
a.CmdBuff().AddListener(a)
|
||||
|
|
@ -122,15 +122,6 @@ func (a *App) Init(version string, rate int) {
|
|||
a.Main.AddPage("main", main, true, false)
|
||||
a.Main.AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
|
||||
|
||||
// ctx := context.WithValue(context.Background(), ui.KeyApp, a)
|
||||
// a.Content.Init(ctx)
|
||||
// d := NewDetails(a, nil)
|
||||
// d.SetText("Fuck!!")
|
||||
// a.Content.Push(d)
|
||||
// d = NewDetails(a, nil)
|
||||
// d.SetText("Shit!!")
|
||||
// a.Content.Push(d)
|
||||
|
||||
main.AddItem(a.indicator(), 1, 1, false)
|
||||
main.AddItem(a.Content, 0, 10, true)
|
||||
main.AddItem(a.Crumbs(), 2, 1, false)
|
||||
|
|
@ -138,31 +129,12 @@ func (a *App) Init(version string, rate int) {
|
|||
a.toggleHeader(!a.Config.K9s.GetHeadless())
|
||||
}
|
||||
|
||||
// func (a *App) StackPushed(c model.Component) {
|
||||
// ctx := context.WithValue(context.Background(), ui.KeyApp, a)
|
||||
// ctx, a.cancelFn = context.WithCancel(context.Background())
|
||||
// c.Init(ctx)
|
||||
|
||||
// a.Frame().AddPage(c.Name(), c, true, true)
|
||||
// a.SetFocus(c)
|
||||
// a.setHints(c.Hints())
|
||||
// }
|
||||
|
||||
// func (a *App) StackPopped(o, c model.Component) {
|
||||
// a.Frame().RemovePage(o.Name())
|
||||
// if c != nil {
|
||||
// a.StackPushed(c)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func (a *App) StackTop(model.Component) {
|
||||
// }
|
||||
|
||||
// Changed indicates the buffer was changed.
|
||||
func (a *App) BufferChanged(s string) {}
|
||||
|
||||
// Active indicates the buff activity changed.
|
||||
func (a *App) BufferActive(state bool, _ ui.BufferKind) {
|
||||
log.Debug().Msgf("App Buffer Activated!")
|
||||
flex, ok := a.Main.GetPrimitive("main").(*tview.Flex)
|
||||
if !ok {
|
||||
return
|
||||
|
|
@ -258,7 +230,9 @@ func (a *App) switchNS(ns string) bool {
|
|||
log.Debug().Msgf("Namespace did not change %s", ns)
|
||||
return true
|
||||
}
|
||||
a.Config.SetActiveNamespace(ns)
|
||||
if err := a.Config.SetActiveNamespace(ns); err != nil {
|
||||
log.Error().Err(err).Msg("Config Set NS failed!")
|
||||
}
|
||||
|
||||
return a.startInformer(ns)
|
||||
}
|
||||
|
|
@ -276,7 +250,9 @@ func (a *App) switchCtx(ctx string, load bool) error {
|
|||
}
|
||||
a.startInformer(ns)
|
||||
a.Config.Reset()
|
||||
a.Config.Save()
|
||||
if err := a.Config.Save(); err != nil {
|
||||
log.Error().Err(err).Msg("Config save failed!")
|
||||
}
|
||||
a.Flash().Infof("Switching context to %s", ctx)
|
||||
if load {
|
||||
a.gotoResource("po", true)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/perf"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
|
|
@ -22,8 +21,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
benchTitle = "Benchmarks"
|
||||
benchTitleFmt = " [seagreen::b]%s([fuchsia::b]%d[fuchsia::-])[seagreen::-] "
|
||||
benchTitle = "Benchmarks"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -42,13 +40,14 @@ type Bench struct {
|
|||
cancelFn context.CancelFunc
|
||||
}
|
||||
|
||||
func NewBench(title, gvr string, _ resource.List) ResourceViewer {
|
||||
// NewBench returns a new viewer.
|
||||
func NewBench(_, _ string, _ resource.List) ResourceViewer {
|
||||
return &Bench{
|
||||
MasterDetail: NewMasterDetail(title),
|
||||
MasterDetail: NewMasterDetail(benchTitle, ""),
|
||||
}
|
||||
}
|
||||
|
||||
// Init the view.
|
||||
// Init initializes the viewer.
|
||||
func (b *Bench) Init(ctx context.Context) {
|
||||
b.MasterDetail.Init(ctx)
|
||||
b.keyBindings()
|
||||
|
|
@ -101,7 +100,7 @@ func (b *Bench) refresh() {
|
|||
|
||||
func (b *Bench) keyBindings() {
|
||||
aa := ui.KeyActions{
|
||||
ui.KeyP: ui.NewKeyAction("Previous", b.app.PrevCmd, false),
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", b.app.PrevCmd, false),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Enter", b.enterCmd, false),
|
||||
tcell.KeyCtrlD: ui.NewKeyAction("Delete", b.deleteCmd, false),
|
||||
}
|
||||
|
|
@ -112,16 +111,6 @@ func (b *Bench) getTitle() string {
|
|||
return benchTitle
|
||||
}
|
||||
|
||||
func (b *Bench) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
tv := b.masterPage()
|
||||
tv.SetSortCol(tv.NameColIndex()+col, 0, asc)
|
||||
tv.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bench) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if b.masterPage().SearchBuff().IsActive() {
|
||||
return b.masterPage().filterCmd(evt)
|
||||
|
|
@ -172,14 +161,6 @@ func (b *Bench) benchFile() string {
|
|||
return ui.TrimCell(b.masterPage().Table, r, 7)
|
||||
}
|
||||
|
||||
func (b *Bench) Hints() model.MenuHints {
|
||||
if h, ok := b.CurrentPage().Item.(model.Hinter); ok {
|
||||
return h.Hints()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bench) hydrate() resource.TableData {
|
||||
ff, err := loadBenchDir(b.app.Config)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color {
|
|||
case "Completed":
|
||||
return ui.CompletedColor
|
||||
case "Running":
|
||||
case "Terminating":
|
||||
return ui.KillColor
|
||||
default:
|
||||
c = ui.ErrColor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,10 @@ func (c *command) run(cmd string) bool {
|
|||
switch cmds[0] {
|
||||
case "ctx", "context", "contexts":
|
||||
if len(cmds) == 2 {
|
||||
c.app.switchCtx(cmds[1], true)
|
||||
if err := c.app.switchCtx(cmds[1], true); err != nil {
|
||||
log.Error().Err(err).Msg("Context switch failed!")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
view := c.componentFor(gvr, v)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
|
@ -25,13 +26,19 @@ func NewContainer(title string, list resource.List, path string) ResourceViewer
|
|||
LogResource: NewLogResource(title, "", list),
|
||||
}
|
||||
c.path = &path
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (c *Container) Init(ctx context.Context) {
|
||||
c.envFn = c.k9sEnv
|
||||
c.containerFn = c.selectedContainer
|
||||
c.extraActionsFn = c.extraActions
|
||||
c.enterFn = c.viewLogs
|
||||
c.colorerFn = containerColorer
|
||||
|
||||
return &c
|
||||
c.LogResource.Init(ctx)
|
||||
}
|
||||
|
||||
// Start starts the component.
|
||||
|
|
@ -49,8 +56,6 @@ func (c *Container) extraActions(aa ui.KeyActions) {
|
|||
aa[ui.KeyShiftF] = ui.NewKeyAction("PortForward", c.portFwdCmd, true)
|
||||
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", c.prevLogsCmd, true)
|
||||
aa[ui.KeyS] = ui.NewKeyAction("Shell", c.shellCmd, true)
|
||||
aa[tcell.KeyEscape] = ui.NewKeyAction("Back", c.backCmd, false)
|
||||
aa[ui.KeyP] = ui.NewKeyAction("Previous", c.backCmd, false)
|
||||
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", c.sortColCmd(6, false), false)
|
||||
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", c.sortColCmd(7, false), false)
|
||||
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", c.sortColCmd(8, false), false)
|
||||
|
|
@ -169,7 +174,3 @@ func (c *Container) runForward(pf *k8s.PortForward, f *portforward.PortForwarder
|
|||
pf.SetActive(false)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Container) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return c.app.PrevCmd(evt)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestContainerNew(t *testing.T) {
|
||||
po := view.NewContainer("Container", resource.NewContainerList(nil, nil), "fred/blee")
|
||||
po.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "containers", po.Name())
|
||||
assert.Equal(t, 22, len(po.Hints()))
|
||||
}
|
||||
|
|
@ -53,7 +53,9 @@ func (c *Context) useContext(name string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
c.app.switchCtx(name, false)
|
||||
if err := c.app.switchCtx(name, false); err != nil {
|
||||
return err
|
||||
}
|
||||
c.refresh()
|
||||
if tv, ok := c.GetPrimitive("ctx").(*Table); ok {
|
||||
tv.Select(1, 0)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
|
||||
const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
||||
|
||||
// Details presents a generic text viewer.
|
||||
// Details represents a generic text viewer.
|
||||
type Details struct {
|
||||
*tview.TextView
|
||||
|
||||
|
|
@ -40,6 +40,7 @@ func NewDetails(app *App, backFn ui.ActionHandler) *Details {
|
|||
}
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (d *Details) Init(ctx context.Context) {
|
||||
d.app = ctx.Value(ui.KeyApp).(*App)
|
||||
|
||||
|
|
@ -63,9 +64,14 @@ func (d *Details) Init(ctx context.Context) {
|
|||
})
|
||||
}
|
||||
|
||||
// Name returns the component name.
|
||||
func (d *Details) Name() string { return "details" }
|
||||
func (d *Details) Start() {}
|
||||
func (d *Details) Stop() {}
|
||||
|
||||
// Start starts the view updater.
|
||||
func (d *Details) Start() {}
|
||||
|
||||
// Stop terminates the updater.
|
||||
func (d *Details) Stop() {}
|
||||
|
||||
func (d *Details) bindKeys() {
|
||||
d.actions = ui.KeyActions{
|
||||
|
|
@ -219,10 +225,7 @@ func (d *Details) setActions(aa ui.KeyActions) {
|
|||
|
||||
// Hints fetch mmemonic and hints
|
||||
func (d *Details) Hints() model.MenuHints {
|
||||
if d.actions != nil {
|
||||
return d.actions.Hints()
|
||||
}
|
||||
return nil
|
||||
return d.actions.Hints()
|
||||
}
|
||||
|
||||
func (d *Details) refreshTitle() {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,6 @@ const (
|
|||
helpTitleFmt = " [aqua::b]%s "
|
||||
)
|
||||
|
||||
type helpItem struct {
|
||||
key, description string
|
||||
}
|
||||
|
||||
// Help presents a help viewer.
|
||||
type Help struct {
|
||||
*ui.Table
|
||||
|
|
@ -49,13 +45,16 @@ func (v *Help) Init(ctx context.Context) {
|
|||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.SetInputCapture(v.keyboard)
|
||||
v.bindKeys()
|
||||
v.build(v.app.Hint.Peek())
|
||||
v.build(v.app.Content.Previous().Hints())
|
||||
}
|
||||
|
||||
func (v *Help) Name() string { return helpTitle }
|
||||
func (v *Help) Start() {}
|
||||
func (v *Help) Stop() {}
|
||||
func (v *Help) Hints() model.MenuHints { return v.actions.Hints() }
|
||||
func (v *Help) Name() string { return helpTitle }
|
||||
func (v *Help) Start() {}
|
||||
func (v *Help) Stop() {}
|
||||
func (v *Help) Hints() model.MenuHints {
|
||||
log.Debug().Msgf("Help Hints %#v", v.actions.Hints())
|
||||
return v.actions.Hints()
|
||||
}
|
||||
|
||||
func (v *Help) bindKeys() {
|
||||
v.actions = ui.KeyActions{
|
||||
|
|
@ -165,10 +164,6 @@ func (v *Help) showGeneral() model.MenuHints {
|
|||
Mnemonic: "Shift-i",
|
||||
Description: "Invert Sort",
|
||||
},
|
||||
{
|
||||
Mnemonic: "p",
|
||||
Description: "Previous View",
|
||||
},
|
||||
{
|
||||
Mnemonic: ":q",
|
||||
Description: "Quit",
|
||||
|
|
|
|||
|
|
@ -1,29 +1,35 @@
|
|||
package view_test
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/config"
|
||||
// "github.com/derailed/k9s/internal/model"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// v1 "k8s.io/api/core/v1"
|
||||
// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
// )
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// func newNS(n string) v1.Namespace {
|
||||
// return v1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
// Name: n,
|
||||
// }}
|
||||
// }
|
||||
func newNS(n string) v1.Namespace {
|
||||
return v1.Namespace{ObjectMeta: metav1.ObjectMeta{
|
||||
Name: n,
|
||||
}}
|
||||
}
|
||||
|
||||
// func TestHelpNew(t *testing.T) {
|
||||
// a := view.NewApp(config.NewConfig(ks{}))
|
||||
// v := view.NewHelp()
|
||||
// ctx := context.WithValue(ui.KeyApp, app)
|
||||
// v.Init(ctx)
|
||||
func TestHelpNew(t *testing.T) {
|
||||
ctx := makeCtx()
|
||||
|
||||
// app.SetHints(model.MenuHints{{Mnemonic: "blee", Description: "duh"}})
|
||||
app := ctx.Value(ui.KeyApp).(*view.App)
|
||||
po := view.NewPod("Pod", "blee", resource.NewPodList(nil, ""))
|
||||
po.Init(ctx)
|
||||
app.Content.Push(po)
|
||||
|
||||
// assert.Equal(t, "<blee>", v.GetCell(1, 0).Text)
|
||||
// assert.Equal(t, "duh", v.GetCell(1, 1).Text)
|
||||
// }
|
||||
v := view.NewHelp()
|
||||
v.Init(ctx)
|
||||
|
||||
assert.Equal(t, 32, v.GetRowCount())
|
||||
assert.Equal(t, 10, v.GetColumnCount())
|
||||
assert.Equal(t, "<esc>", v.GetCell(1, 0).Text)
|
||||
assert.Equal(t, "Back", v.GetCell(1, 1).Text)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ func (j *Job) extraActions(aa ui.KeyActions) {
|
|||
j.LogResource.extraActions(aa)
|
||||
}
|
||||
|
||||
func (j *Job) showPods(app *App, ns, res, sel string) {
|
||||
func (j *Job) showPods(app *App, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
job, err := k8s.NewJob(app.Conn()).Get(ns, n)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
|
|
@ -17,44 +16,34 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type logFrame struct {
|
||||
// Log represents a generic log viewer.
|
||||
type Log struct {
|
||||
*tview.Flex
|
||||
|
||||
app *App
|
||||
actions ui.KeyActions
|
||||
backFn ui.ActionHandler
|
||||
app *App
|
||||
actions ui.KeyActions
|
||||
backFn ui.ActionHandler
|
||||
logs *Details
|
||||
scrollIndicator *AutoScrollIndicator
|
||||
ansiWriter io.Writer
|
||||
path string
|
||||
}
|
||||
|
||||
func newLogFrame(app *App, backFn ui.ActionHandler) *logFrame {
|
||||
f := logFrame{
|
||||
// NewLog returns a new viewer.
|
||||
func NewLog(_ string, app *App, backFn ui.ActionHandler) *Log {
|
||||
l := Log{
|
||||
Flex: tview.NewFlex(),
|
||||
app: app,
|
||||
backFn: backFn,
|
||||
actions: make(ui.KeyActions),
|
||||
}
|
||||
f.SetBorder(true)
|
||||
f.SetBackgroundColor(config.AsColor(app.Styles.Views().Log.BgColor))
|
||||
f.SetBorderPadding(0, 0, 1, 1)
|
||||
f.SetDirection(tview.FlexRow)
|
||||
l.SetBorder(true)
|
||||
l.SetBackgroundColor(config.AsColor(app.Styles.Views().Log.BgColor))
|
||||
l.SetBorderPadding(0, 0, 1, 1)
|
||||
l.SetDirection(tview.FlexRow)
|
||||
|
||||
return &f
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
*logFrame
|
||||
|
||||
logs *Details
|
||||
status *statusView
|
||||
ansiWriter io.Writer
|
||||
autoScroll int32
|
||||
path string
|
||||
}
|
||||
|
||||
func NewLog(_ string, app *App, backFn ui.ActionHandler) *Log {
|
||||
l := Log{
|
||||
logFrame: newLogFrame(app, backFn),
|
||||
autoScroll: 1,
|
||||
}
|
||||
l.scrollIndicator = NewAutoScrollIndicator(app.Styles)
|
||||
l.AddItem(l.scrollIndicator, 1, 1, false)
|
||||
|
||||
l.logs = NewDetails(app, backFn)
|
||||
{
|
||||
|
|
@ -67,8 +56,6 @@ func NewLog(_ string, app *App, backFn ui.ActionHandler) *Log {
|
|||
l.logs.SetMaxBuffer(app.Config.K9s.LogBufferSize)
|
||||
}
|
||||
l.ansiWriter = tview.ANSIWriter(l.logs, app.Styles.Views().Log.FgColor, app.Styles.Views().Log.BgColor)
|
||||
l.status = newStatusView(app.Styles)
|
||||
l.AddItem(l.status, 1, 1, false)
|
||||
l.AddItem(l.logs, 0, 1, true)
|
||||
|
||||
l.bindKeys()
|
||||
|
|
@ -77,16 +64,26 @@ func NewLog(_ string, app *App, backFn ui.ActionHandler) *Log {
|
|||
return &l
|
||||
}
|
||||
|
||||
// Logs return the viewer logs.
|
||||
func (l *Log) Logs() *Details {
|
||||
return l.logs
|
||||
}
|
||||
|
||||
// ScrollIndicator returns the scroll mode viewer.
|
||||
func (l *Log) ScrollIndicator() *AutoScrollIndicator {
|
||||
return l.scrollIndicator
|
||||
}
|
||||
|
||||
func (l *Log) bindKeys() {
|
||||
l.actions = ui.KeyActions{
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", l.backCmd, true),
|
||||
ui.KeyC: ui.NewKeyAction("Clear", l.clearCmd, true),
|
||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.toggleScrollCmd, true),
|
||||
ui.KeyS: ui.NewKeyAction("Toggle AutoScroll", l.ToggleAutoScrollCmd, true),
|
||||
ui.KeyG: ui.NewKeyAction("Top", l.topCmd, false),
|
||||
ui.KeyShiftG: ui.NewKeyAction("Bottom", l.bottomCmd, false),
|
||||
ui.KeyF: ui.NewKeyAction("Up", l.pageUpCmd, false),
|
||||
ui.KeyB: ui.NewKeyAction("Down", l.pageDownCmd, false),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.saveCmd, true),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", l.SaveCmd, true),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -124,32 +121,24 @@ func (l *Log) log(lines string) {
|
|||
log.Debug().Msgf("LOG LINES %d", l.logs.GetLineCount())
|
||||
}
|
||||
|
||||
func (l *Log) flush(index int, buff []string) {
|
||||
if index == 0 {
|
||||
// Flush write logs to viewer.
|
||||
func (l *Log) Flush(index int, buff []string) {
|
||||
if index == 0 || !l.scrollIndicator.AutoScroll() {
|
||||
return
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&l.autoScroll) == 1 {
|
||||
l.log(strings.Join(buff[:index], "\n"))
|
||||
l.app.QueueUpdateDraw(func() {
|
||||
l.updateIndicator()
|
||||
l.logs.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Log) updateIndicator() {
|
||||
status := "Off"
|
||||
if l.autoScroll == 1 {
|
||||
status = "On"
|
||||
}
|
||||
l.status.update([]string{fmt.Sprintf("Autoscroll: %s", status)})
|
||||
l.log(strings.Join(buff[:index], "\n"))
|
||||
l.app.QueueUpdateDraw(func() {
|
||||
l.scrollIndicator.Refresh()
|
||||
l.logs.ScrollToEnd()
|
||||
})
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Actions...
|
||||
|
||||
func (l *Log) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
// SaveCmd dumps the logs to file.
|
||||
func (l *Log) SaveCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if path, err := saveData(l.app.Config.K9s.CurrentCluster, l.path, l.logs.GetText(true)); err != nil {
|
||||
l.app.Flash().Err(err)
|
||||
} else {
|
||||
|
|
@ -183,28 +172,18 @@ func saveData(cluster, name, data string) (string, error) {
|
|||
log.Error().Err(err).Msgf("LogFile create %s", path)
|
||||
return "", nil
|
||||
}
|
||||
if _, err := fmt.Fprintf(file, data); err != nil {
|
||||
|
||||
if _, err := file.Write([]byte(data)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (l *Log) toggleScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if atomic.LoadInt32(&l.autoScroll) == 0 {
|
||||
atomic.StoreInt32(&l.autoScroll, 1)
|
||||
} else {
|
||||
atomic.StoreInt32(&l.autoScroll, 0)
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&l.autoScroll) == 1 {
|
||||
l.app.Flash().Info("Autoscroll is on.")
|
||||
l.logs.ScrollToEnd()
|
||||
} else {
|
||||
l.logs.LineUp()
|
||||
l.app.Flash().Info("Autoscroll is off.")
|
||||
}
|
||||
l.updateIndicator()
|
||||
// ToggleAutoScrollCmd toggles auto scrolling of logs.
|
||||
func (l *Log) ToggleAutoScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
l.scrollIndicator.ToggleAutoScroll()
|
||||
l.scrollIndicator.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// ContainerFn returns the active container name.
|
||||
|
|
@ -85,8 +86,9 @@ func (l *LogResource) showLogs(prev bool) {
|
|||
}
|
||||
|
||||
func (l *LogResource) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
// Reset namespace to what it was
|
||||
l.app.Config.SetActiveNamespace(l.list.GetNamespace())
|
||||
if err := l.app.Config.SetActiveNamespace(l.list.GetNamespace()); err != nil {
|
||||
log.Error().Err(err).Msg("Config NS set failed!")
|
||||
}
|
||||
l.app.inject(l)
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,81 +1,81 @@
|
|||
package view_test
|
||||
|
||||
// import (
|
||||
// "bytes"
|
||||
// "fmt"
|
||||
// "testing"
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/config"
|
||||
// "github.com/derailed/k9s/internal/view"
|
||||
// "github.com/derailed/tview"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// )
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// func TestAnsi(t *testing.T) {
|
||||
// buff := bytes.NewBufferString("")
|
||||
// w := tview.ANSIWriter(buff, "white", "black")
|
||||
// fmt.Fprintf(w, "[YELLOW] ok")
|
||||
// assert.Equal(t, "[YELLOW] ok", buff.String())
|
||||
func TestAnsi(t *testing.T) {
|
||||
buff := bytes.NewBufferString("")
|
||||
w := tview.ANSIWriter(buff, "white", "black")
|
||||
fmt.Fprintf(w, "[YELLOW] ok")
|
||||
assert.Equal(t, "[YELLOW] ok", buff.String())
|
||||
|
||||
// v := tview.NewTextView()
|
||||
// v.SetDynamicColors(true)
|
||||
// aw := tview.ANSIWriter(v, "white", "black")
|
||||
// s := "[2019-03-27T15:05:15,246][INFO ][o.e.c.r.a.AllocationService] [es-0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-6-2019.03.27][0]]"
|
||||
// fmt.Fprintf(aw, s)
|
||||
// assert.Equal(t, s+"\n", v.GetText(false))
|
||||
// }
|
||||
v := tview.NewTextView()
|
||||
v.SetDynamicColors(true)
|
||||
aw := tview.ANSIWriter(v, "white", "black")
|
||||
s := "[2019-03-27T15:05:15,246][INFO ][o.e.c.r.a.AllocationService] [es-0] Cluster health status changed from [YELLOW] to [GREEN] (reason: [shards started [[.monitoring-es-6-2019.03.27][0]]"
|
||||
fmt.Fprintf(aw, s)
|
||||
assert.Equal(t, s+"\n", v.GetText(false))
|
||||
}
|
||||
|
||||
// func TestLogFlush(t *testing.T) {
|
||||
// v := view.NewLog("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
// v.flush(2, []string{"blee", "bozo"})
|
||||
func TestLogFlush(t *testing.T) {
|
||||
v := view.NewLog("Logs", makeApp(), nil)
|
||||
v.Flush(2, []string{"blee", "bozo"})
|
||||
|
||||
// v.toggleScrollCmd(nil)
|
||||
// assert.Equal(t, "blee\nbozo\n", v.logs.GetText(true))
|
||||
// assert.Equal(t, " Autoscroll: Off ", v.status.GetText(true))
|
||||
// v.toggleScrollCmd(nil)
|
||||
// assert.Equal(t, " Autoscroll: On ", v.status.GetText(true))
|
||||
// }
|
||||
v.ToggleAutoScrollCmd(nil)
|
||||
assert.Equal(t, "blee\nbozo\n", v.Logs().GetText(true))
|
||||
assert.Equal(t, " Autoscroll: Off ", v.ScrollIndicator().GetText(true))
|
||||
v.ToggleAutoScrollCmd(nil)
|
||||
assert.Equal(t, " Autoscroll: On ", v.ScrollIndicator().GetText(true))
|
||||
assert.Equal(t, 8, len(v.Hints()))
|
||||
}
|
||||
|
||||
// func TestLogViewSave(t *testing.T) {
|
||||
// v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
// v.flush(2, []string{"blee", "bozo"})
|
||||
// v.path = "k9s-test"
|
||||
// dir := filepath.Join(config.K9sDumpDir, v.app.Config.K9s.CurrentCluster)
|
||||
// c1, _ := ioutil.ReadDir(dir)
|
||||
// v.saveCmd(nil)
|
||||
// c2, _ := ioutil.ReadDir(dir)
|
||||
// assert.Equal(t, len(c2), len(c1)+1)
|
||||
// }
|
||||
func TestLogViewSave(t *testing.T) {
|
||||
app := makeApp()
|
||||
v := view.NewLog("Logs", app, nil)
|
||||
v.Flush(2, []string{"blee", "bozo"})
|
||||
config.K9sDumpDir = "/tmp"
|
||||
dir := filepath.Join(config.K9sDumpDir, app.Config.K9s.CurrentCluster)
|
||||
c1, _ := ioutil.ReadDir(dir)
|
||||
v.SaveCmd(nil)
|
||||
c2, _ := ioutil.ReadDir(dir)
|
||||
assert.Equal(t, len(c2), len(c1)+1)
|
||||
}
|
||||
|
||||
// func TestLogViewNav(t *testing.T) {
|
||||
// v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
// var buff []string
|
||||
// v.autoScroll = 1
|
||||
// for i := 0; i < 100; i++ {
|
||||
// buff = append(buff, fmt.Sprintf("line-%d\n", i))
|
||||
// }
|
||||
// v.flush(100, buff)
|
||||
func TestLogViewNav(t *testing.T) {
|
||||
v := view.NewLog("Logs", makeApp(), nil)
|
||||
var buff []string
|
||||
for i := 0; i < 100; i++ {
|
||||
buff = append(buff, fmt.Sprintf("line-%d\n", i))
|
||||
}
|
||||
v.Flush(100, buff)
|
||||
v.ToggleAutoScrollCmd(nil)
|
||||
|
||||
// v.topCmd(nil)
|
||||
// r, _ := v.logs.GetScrollOffset()
|
||||
// assert.Equal(t, 0, r)
|
||||
// v.pageDownCmd(nil)
|
||||
// r, _ = v.logs.GetScrollOffset()
|
||||
// assert.Equal(t, 0, r)
|
||||
// v.pageUpCmd(nil)
|
||||
// r, _ = v.logs.GetScrollOffset()
|
||||
// assert.Equal(t, 0, r)
|
||||
// v.bottomCmd(nil)
|
||||
// r, _ = v.logs.GetScrollOffset()
|
||||
// assert.Equal(t, 0, r)
|
||||
// }
|
||||
r, _ := v.Logs().GetScrollOffset()
|
||||
assert.Equal(t, -1, r)
|
||||
}
|
||||
|
||||
// func TestLogViewClear(t *testing.T) {
|
||||
// v := newLogView("Logs", NewApp(config.NewConfig(ks{})), nil)
|
||||
// v.flush(2, []string{"blee", "bozo"})
|
||||
func TestLogViewClear(t *testing.T) {
|
||||
v := view.NewLog("Logs", makeApp(), nil)
|
||||
v.Flush(2, []string{"blee", "bozo"})
|
||||
|
||||
// v.toggleScrollCmd(nil)
|
||||
// assert.Equal(t, "blee\nbozo\n", v.logs.GetText(true))
|
||||
// v.clearCmd(nil)
|
||||
// assert.Equal(t, "", v.logs.GetText(true))
|
||||
// }
|
||||
v.ToggleAutoScrollCmd(nil)
|
||||
assert.Equal(t, "blee\nbozo\n", v.Logs().GetText(true))
|
||||
v.Logs().Clear()
|
||||
assert.Equal(t, "", v.Logs().GetText(true))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func makeApp() *view.App {
|
||||
return view.NewApp(config.NewConfig(ks{}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,31 +14,24 @@ import (
|
|||
|
||||
const (
|
||||
logBuffSize = 100
|
||||
flushTimeout = 200 * time.Millisecond
|
||||
FlushTimeout = 200 * time.Millisecond
|
||||
|
||||
logCoFmt = " Logs([fg:bg:]%s:[hilite:bg:b]%s[-:bg:-]) "
|
||||
logFmt = " Logs([fg:bg:]%s) "
|
||||
)
|
||||
|
||||
type (
|
||||
masterView interface {
|
||||
backFn() ui.ActionHandler
|
||||
App() *App
|
||||
}
|
||||
// Logs presents a collection of logs.
|
||||
type Logs struct {
|
||||
*ui.Pages
|
||||
|
||||
// Logs presents a collection of logs.
|
||||
Logs struct {
|
||||
*ui.Pages
|
||||
|
||||
app *App
|
||||
parent loggable
|
||||
actions ui.KeyActions
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
)
|
||||
app *App
|
||||
parent Loggable
|
||||
actions ui.KeyActions
|
||||
cancelFunc context.CancelFunc
|
||||
}
|
||||
|
||||
// NewLogs returns a new logs viewer.
|
||||
func NewLogs(title string, parent loggable) *Logs {
|
||||
func NewLogs(title string, parent Loggable) *Logs {
|
||||
return &Logs{
|
||||
Pages: ui.NewPages(),
|
||||
parent: parent,
|
||||
|
|
@ -55,7 +48,7 @@ func (l *Logs) Name() string { return "logs" }
|
|||
|
||||
// Protocol...
|
||||
|
||||
func (l *Logs) reload(co string, parent loggable, prevLogs bool) {
|
||||
func (l *Logs) reload(co string, parent Loggable, prevLogs bool) {
|
||||
l.parent = parent
|
||||
l.deletePage()
|
||||
l.AddPage("logs", NewLog(co, l.app, l.backCmd), true, true)
|
||||
|
|
@ -152,7 +145,7 @@ func updateLogs(ctx context.Context, c <-chan string, l *Log, buffSize int) {
|
|||
case line, ok := <-c:
|
||||
if !ok {
|
||||
log.Debug().Msgf("Closed channel detected. Bailing out...")
|
||||
l.flush(index, buff)
|
||||
l.Flush(index, buff)
|
||||
return
|
||||
}
|
||||
if index < buffSize {
|
||||
|
|
@ -160,12 +153,12 @@ func updateLogs(ctx context.Context, c <-chan string, l *Log, buffSize int) {
|
|||
index++
|
||||
continue
|
||||
}
|
||||
l.flush(index, buff)
|
||||
l.Flush(index, buff)
|
||||
index = 0
|
||||
buff[index] = line
|
||||
index++
|
||||
case <-time.After(flushTimeout):
|
||||
l.flush(index, buff)
|
||||
case <-time.After(FlushTimeout):
|
||||
l.Flush(index, buff)
|
||||
index = 0
|
||||
case <-ctx.Done():
|
||||
return
|
||||
|
|
|
|||
|
|
@ -3,7 +3,11 @@ package view
|
|||
import (
|
||||
"context"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// MasterDetail presents a master-detail viewer.
|
||||
|
|
@ -18,23 +22,42 @@ type MasterDetail struct {
|
|||
}
|
||||
|
||||
// NewMasterDetail returns a new master-detail viewer.
|
||||
func NewMasterDetail(title string) *MasterDetail {
|
||||
func NewMasterDetail(title, ns string) *MasterDetail {
|
||||
return &MasterDetail{
|
||||
PageStack: NewPageStack(),
|
||||
title: title,
|
||||
currentNS: ns,
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (m *MasterDetail) Init(ctx context.Context) {
|
||||
log.Debug().Msgf("\t>>>MasterDetail init %q", m.title)
|
||||
app := ctx.Value(ui.KeyApp).(*App)
|
||||
if m.currentNS != resource.NotNamespaced {
|
||||
m.currentNS = app.Config.ActiveNamespace()
|
||||
}
|
||||
m.PageStack.Init(ctx)
|
||||
m.AddListener(app.Menu())
|
||||
|
||||
t := NewTable(m.title)
|
||||
t.Init(ctx)
|
||||
m.Push(t)
|
||||
|
||||
m.details = NewDetails(m.app, nil)
|
||||
m.details = NewDetails(m.app, func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
m.Pop()
|
||||
return nil
|
||||
})
|
||||
m.details.Init(ctx)
|
||||
log.Debug().Msgf("\t<<<<MasterDetail INIT DONE!!")
|
||||
}
|
||||
|
||||
// Hints returns the current viewer hints
|
||||
func (m *MasterDetail) Hints() model.MenuHints {
|
||||
if c, ok := m.Top().(model.Hinter); ok {
|
||||
return c.Hints()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MasterDetail) setExtraActionsFn(f ActionsFunc) {
|
||||
|
|
@ -63,14 +86,46 @@ func (m *MasterDetail) detailsPage() *Details {
|
|||
return m.details
|
||||
}
|
||||
|
||||
func (m *MasterDetail) isMaster() bool {
|
||||
p := m.CurrentPage()
|
||||
if p == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug().Msgf("!!!!!Checking MASTER %q vs %q -- %t", p.Name, m.title, p.Name == m.title)
|
||||
return p.Name == m.title
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Actions...
|
||||
|
||||
func (m *MasterDetail) defaultActions(aa ui.KeyActions) {
|
||||
aa[ui.KeyHelp] = ui.NewKeyAction("Help", noopCmd, false)
|
||||
aa[ui.KeyP] = ui.NewKeyAction("Previous", m.app.PrevCmd, false)
|
||||
aa[tcell.KeyEsc] = ui.NewKeyAction("Back", m.backCmd, false)
|
||||
|
||||
if m.extraActionsFn != nil {
|
||||
m.extraActionsFn(aa)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MasterDetail) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !m.isMaster() {
|
||||
return m.app.PrevCmd(evt)
|
||||
}
|
||||
|
||||
if m.masterPage().resetCmd(evt) != nil {
|
||||
return m.app.PrevCmd(evt)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MasterDetail) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
t := m.masterPage()
|
||||
t.SetSortCol(t.NameColIndex()+col, 0, asc)
|
||||
t.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
// Node represents a node view.
|
||||
|
|
@ -29,16 +30,6 @@ func (n *Node) extraActions(aa ui.KeyActions) {
|
|||
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", n.sortColCmd(10, false), false)
|
||||
}
|
||||
|
||||
func (n *Node) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
t := n.masterPage()
|
||||
t.SetSortCol(t.NameColIndex()+col, 0, asc)
|
||||
t.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) showPods(app *App, _, _, sel string) {
|
||||
showPods(app, "", "", "spec.nodeName="+sel, n.backCmd)
|
||||
}
|
||||
|
|
@ -59,9 +50,12 @@ func showPods(app *App, ns, labelSel, fieldSel string, a ui.ActionHandler) {
|
|||
|
||||
v := NewPod("Pod", "v1/pods", list)
|
||||
v.setColorerFn(podColorer)
|
||||
v.masterPage().AddActions(ui.KeyActions{
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", a, true),
|
||||
})
|
||||
app.Config.SetActiveNamespace(ns)
|
||||
// BOZO!!
|
||||
// v.masterPage().AddActions(ui.KeyActions{
|
||||
// tcell.KeyEsc: ui.NewKeyAction("Back", a, true),
|
||||
// })
|
||||
if err := app.Config.SetActiveNamespace(ns); err != nil {
|
||||
log.Error().Err(err).Msg("Config NS set failed!")
|
||||
}
|
||||
app.inject(v)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import (
|
|||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
const (
|
||||
favNSIndicator = "+"
|
||||
defaultNSIndicator = "(*)"
|
||||
deltaNSIndicator = "(𝜟)"
|
||||
)
|
||||
|
||||
var nsCleanser = regexp.MustCompile(`(\w+)[+|(*)|(𝜟)]*`)
|
||||
|
|
@ -59,7 +59,9 @@ func (n *Namespace) useNamespace(ns string) {
|
|||
} else {
|
||||
n.app.Flash().Infof("Namespace %s is now active!", ns)
|
||||
}
|
||||
n.app.Config.Save()
|
||||
if err := n.app.Config.Save(); err != nil {
|
||||
log.Error().Err(err).Msg("Config file save failed!")
|
||||
}
|
||||
n.app.startInformer(ns)
|
||||
}
|
||||
|
||||
|
|
@ -79,11 +81,11 @@ func (n *Namespace) decorate(data resource.TableData) resource.TableData {
|
|||
}
|
||||
for k, r := range data.Rows {
|
||||
if config.InList(n.app.Config.FavNamespaces(), k) {
|
||||
r.Fields[0] += "+"
|
||||
r.Fields[0] += favNSIndicator
|
||||
r.Action = resource.Unchanged
|
||||
}
|
||||
if n.app.Config.ActiveNamespace() == k {
|
||||
r.Fields[0] += "(*)"
|
||||
r.Fields[0] += defaultNSIndicator
|
||||
r.Action = resource.Unchanged
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type PageStack struct {
|
||||
|
|
@ -22,28 +21,14 @@ func NewPageStack() *PageStack {
|
|||
|
||||
func (p *PageStack) Init(ctx context.Context) {
|
||||
p.app = ctx.Value(ui.KeyApp).(*App)
|
||||
|
||||
p.Pages.SetChangedFunc(func() {
|
||||
log.Debug().Msgf(">>>>>PS CHNGED<<<<<")
|
||||
p.DumpStack()
|
||||
active := p.CurrentPage()
|
||||
if active == nil {
|
||||
return
|
||||
}
|
||||
c := active.Item.(model.Component)
|
||||
log.Debug().Msgf("-------Page activated %#v", active)
|
||||
p.app.Hint.SetHints(c.Hints())
|
||||
})
|
||||
|
||||
p.Pages.SetTitle("Fuck!")
|
||||
p.Stack.AddListener(p)
|
||||
}
|
||||
|
||||
func (p *PageStack) StackPushed(c model.Component) {
|
||||
ctx := context.WithValue(context.Background(), ui.KeyApp, p.app)
|
||||
c.Init(ctx)
|
||||
c.Start()
|
||||
p.app.SetFocus(c)
|
||||
p.app.Hint.SetHints(c.Hints())
|
||||
}
|
||||
|
||||
func (p *PageStack) StackPopped(o, top model.Component) {
|
||||
|
|
@ -57,5 +42,4 @@ func (p *PageStack) StackTop(top model.Component) {
|
|||
}
|
||||
top.Start()
|
||||
p.app.SetFocus(top)
|
||||
p.app.Hint.SetHints(top.Hints())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
|
|
@ -18,12 +19,14 @@ const (
|
|||
shellCheck = "command -v bash >/dev/null && exec bash || exec sh"
|
||||
)
|
||||
|
||||
type loggable interface {
|
||||
type Loggable interface {
|
||||
getSelection() string
|
||||
getList() resource.List
|
||||
Pop() (model.Component, bool)
|
||||
}
|
||||
|
||||
var _ Loggable = &Pod{}
|
||||
|
||||
// Pod represents a pod viewer.
|
||||
type Pod struct {
|
||||
*Resource
|
||||
|
|
@ -34,20 +37,23 @@ type Pod struct {
|
|||
|
||||
// NewPod returns a new viewer.
|
||||
func NewPod(title, gvr string, list resource.List) ResourceViewer {
|
||||
p := Pod{
|
||||
return &Pod{
|
||||
Resource: NewResource(title, gvr, list),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (p *Pod) Init(ctx context.Context) {
|
||||
p.extraActionsFn = p.extraActions
|
||||
p.enterFn = p.listContainers
|
||||
p.Resource.Init(ctx)
|
||||
|
||||
p.picker = newSelectList(&p)
|
||||
p.picker = newSelectList(p)
|
||||
p.picker.setActions(ui.KeyActions{
|
||||
tcell.KeyEscape: {Description: "Back", Action: p.backCmd, Visible: true},
|
||||
})
|
||||
|
||||
p.logs = NewLogs(list.GetName(), &p)
|
||||
|
||||
return &p
|
||||
p.logs = NewLogs(p.list.GetName(), p)
|
||||
p.logs.Init(ctx)
|
||||
}
|
||||
|
||||
func (p *Pod) extraActions(aa ui.KeyActions) {
|
||||
|
|
@ -143,9 +149,8 @@ func (p *Pod) viewLogs(prev bool) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (p *Pod) showLogs(path, co string, parent loggable, prev bool) {
|
||||
l := p.GetPrimitive("logs").(*Logs)
|
||||
l.reload(co, parent, prev)
|
||||
func (p *Pod) showLogs(path, co string, parent Loggable, prev bool) {
|
||||
p.logs.reload(co, parent, prev)
|
||||
p.Push(p.logs)
|
||||
}
|
||||
|
||||
|
|
@ -180,16 +185,6 @@ func (p *Pod) shellIn(path, co string) {
|
|||
p.Start()
|
||||
}
|
||||
|
||||
func (p *Pod) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
t := p.masterPage()
|
||||
t.SetSortCol(t.NameColIndex()+col, 0, asc)
|
||||
t.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
|
|
|
|||
|
|
@ -1,15 +1,27 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPodNew(t *testing.T) {
|
||||
po := view.NewPod("test", "blee", resource.NewPodList(nil, ""))
|
||||
po := view.NewPod("Pod", "blee", resource.NewPodList(nil, ""))
|
||||
po.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "po", po.Name())
|
||||
assert.Equal(t, "pods", po.Name())
|
||||
assert.Equal(t, 31, len(po.Hints()))
|
||||
}
|
||||
|
||||
// Helpers...
|
||||
|
||||
func makeCtx() context.Context {
|
||||
cfg := config.NewConfig(ks{})
|
||||
return context.WithValue(context.Background(), ui.KeyApp, view.NewApp(cfg))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
|
|
@ -85,9 +84,8 @@ func (p *Policy) bindKeys() {
|
|||
p.RmAction(ui.KeyShiftA)
|
||||
|
||||
p.AddActions(ui.KeyActions{
|
||||
tcell.KeyEscape: ui.NewKeyAction("Reset", p.resetCmd, false),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false),
|
||||
ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false),
|
||||
ui.KeyP: ui.NewKeyAction("Previous", p.app.PrevCmd, false),
|
||||
ui.KeyShiftS: ui.NewKeyAction("Sort Namespace", p.SortColCmd(0), false),
|
||||
ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.SortColCmd(1), false),
|
||||
ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.SortColCmd(2), false),
|
||||
|
|
@ -130,10 +128,6 @@ func (p *Policy) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return p.app.PrevCmd(evt)
|
||||
}
|
||||
|
||||
func (p *Policy) Hints() model.MenuHints {
|
||||
return p.Hints()
|
||||
}
|
||||
|
||||
func (p *Policy) reconcile() (resource.TableData, error) {
|
||||
var table resource.TableData
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,14 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
forwardTitle = "Port Forwards"
|
||||
forwardTitleFmt = " [aqua::b]%s([fuchsia::b]%d[fuchsia::-])[aqua::-] "
|
||||
promptPage = "prompt"
|
||||
portForwardTitle = "PortForwards"
|
||||
portForwardTitleFmt = " [aqua::b]%s([fuchsia::b]%d[fuchsia::-])[aqua::-] "
|
||||
promptPage = "prompt"
|
||||
)
|
||||
|
||||
// PortForward presents active portforward viewer.
|
||||
type PortForward struct {
|
||||
*ui.Pages
|
||||
*MasterDetail
|
||||
|
||||
cancelFn context.CancelFunc
|
||||
bench *perf.Benchmark
|
||||
|
|
@ -34,27 +34,26 @@ type PortForward struct {
|
|||
}
|
||||
|
||||
// NewPortForward returns a new viewer.
|
||||
func NewPortForward(title, _ string, list resource.List) ResourceViewer {
|
||||
func NewPortForward(title, gvr string, list resource.List) ResourceViewer {
|
||||
return &PortForward{
|
||||
Pages: ui.NewPages(),
|
||||
MasterDetail: NewMasterDetail(portForwardTitle, ""),
|
||||
}
|
||||
}
|
||||
|
||||
// Init the view.
|
||||
func (p *PortForward) Init(ctx context.Context) {
|
||||
p.app = ctx.Value(ui.KeyApp).(*App)
|
||||
p.MasterDetail.Init(ctx)
|
||||
p.registerActions()
|
||||
|
||||
tv := NewTable(forwardTitle)
|
||||
tv.Init(ctx)
|
||||
tv := p.masterPage()
|
||||
tv.SetBorderFocusColor(tcell.ColorDodgerBlue)
|
||||
tv.SetSelectedStyle(tcell.ColorWhite, tcell.ColorDodgerBlue, tcell.AttrNone)
|
||||
tv.SetColorerFn(forwardColorer)
|
||||
tv.SetActiveNS("")
|
||||
tv.SetSortCol(tv.NameColIndex()+6, 0, true)
|
||||
tv.Select(1, 0)
|
||||
p.Push(tv)
|
||||
|
||||
p.registerActions()
|
||||
p.Start()
|
||||
p.refresh()
|
||||
}
|
||||
|
|
@ -71,11 +70,7 @@ func (p *PortForward) Start() {
|
|||
func (p *PortForward) Stop() {}
|
||||
|
||||
func (p *PortForward) Name() string {
|
||||
return "portForwards"
|
||||
}
|
||||
|
||||
func (p *PortForward) masterPage() *Table {
|
||||
return p.GetPrimitive("table").(*Table)
|
||||
return portForwardTitle
|
||||
}
|
||||
|
||||
func (p *PortForward) setEnterFn(enterFn) {}
|
||||
|
|
@ -83,13 +78,6 @@ func (p *PortForward) setColorerFn(ui.ColorerFunc) {}
|
|||
func (p *PortForward) setDecorateFn(decorateFn) {}
|
||||
func (p *PortForward) setExtraActionsFn(ActionsFunc) {}
|
||||
|
||||
func (p *PortForward) getTV() *Table {
|
||||
if vu, ok := p.GetPrimitive("table").(*Table); ok {
|
||||
return vu
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PortForward) reload() {
|
||||
path := ui.BenchConfig(p.app.Config.K9s.CurrentCluster)
|
||||
log.Debug().Msgf("Reloading Config %s", path)
|
||||
|
|
@ -100,38 +88,28 @@ func (p *PortForward) reload() {
|
|||
}
|
||||
|
||||
func (p *PortForward) refresh() {
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
tv.Update(p.hydrate())
|
||||
p.app.SetFocus(tv)
|
||||
tv.UpdateTitle()
|
||||
}
|
||||
|
||||
func (p *PortForward) registerActions() {
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
tv.AddActions(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Goto", p.gotoBenchCmd, true),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.gotoBenchCmd, true),
|
||||
tcell.KeyCtrlB: ui.NewKeyAction("Bench", p.benchCmd, true),
|
||||
tcell.KeyCtrlK: ui.NewKeyAction("Bench Stop", p.benchStopCmd, true),
|
||||
tcell.KeyCtrlD: ui.NewKeyAction("Delete", p.deleteCmd, true),
|
||||
ui.KeySlash: ui.NewKeyAction("Filter", tv.activateCmd, false),
|
||||
ui.KeyP: ui.NewKeyAction("Previous", p.app.PrevCmd, false),
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", p.app.PrevCmd, false),
|
||||
ui.KeyShiftP: ui.NewKeyAction("Sort Ports", p.sortColCmd(2, true), false),
|
||||
ui.KeyShiftU: ui.NewKeyAction("Sort URL", p.sortColCmd(4, true), false),
|
||||
})
|
||||
}
|
||||
|
||||
func (p *PortForward) getTitle() string {
|
||||
return forwardTitle
|
||||
}
|
||||
|
||||
func (p *PortForward) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
tv := p.getTV()
|
||||
tv.SetSortCol(tv.NameColIndex()+col, 0, asc)
|
||||
p.refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
return portForwardTitle
|
||||
}
|
||||
|
||||
func (p *PortForward) gotoBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
|
|
@ -162,7 +140,7 @@ func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
r, _ := tv.GetSelection()
|
||||
cfg, co := defaultConfig(), ui.TrimCell(tv.Table, r, 2)
|
||||
if b, ok := p.app.Bench.Benchmarks.Containers[containerID(sel, co)]; ok {
|
||||
|
|
@ -205,7 +183,7 @@ func (p *PortForward) runBenchmark() {
|
|||
}
|
||||
|
||||
func (p *PortForward) getSelectedItem() string {
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
r, _ := tv.GetSelection()
|
||||
if r == 0 {
|
||||
return ""
|
||||
|
|
@ -217,7 +195,7 @@ func (p *PortForward) getSelectedItem() string {
|
|||
}
|
||||
|
||||
func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
if !tv.SearchBuff().Empty() {
|
||||
tv.SearchBuff().Reset()
|
||||
return nil
|
||||
|
|
@ -238,7 +216,7 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
delete(p.app.forwarders, sel)
|
||||
|
||||
log.Debug().Msgf("PortForwards after delete: %#v", p.app.forwarders)
|
||||
p.getTV().Update(p.hydrate())
|
||||
p.masterPage().Update(p.hydrate())
|
||||
p.app.Flash().Infof("PortForward %s deleted!", sel)
|
||||
})
|
||||
|
||||
|
|
@ -250,7 +228,7 @@ func (p *PortForward) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
p.cancelFn()
|
||||
}
|
||||
|
||||
tv := p.getTV()
|
||||
tv := p.masterPage()
|
||||
if tv.SearchBuff().IsActive() {
|
||||
tv.SearchBuff().Reset()
|
||||
} else {
|
||||
|
|
@ -260,10 +238,6 @@ func (p *PortForward) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (p *PortForward) Hints() model.MenuHints {
|
||||
return p.getTV().Hints()
|
||||
}
|
||||
|
||||
func (p *PortForward) hydrate() resource.TableData {
|
||||
data := initHeader(len(p.app.forwarders))
|
||||
dc, dn := p.app.Bench.Benchmarks.Defaults.C, p.app.Bench.Benchmarks.Defaults.N
|
||||
|
|
@ -293,7 +267,7 @@ func (p *PortForward) hydrate() resource.TableData {
|
|||
}
|
||||
|
||||
func (p *PortForward) resetTitle() {
|
||||
p.SetTitle(fmt.Sprintf(forwardTitleFmt, forwardTitle, p.getTV().GetRowCount()-1))
|
||||
p.SetTitle(fmt.Sprintf(portForwardTitleFmt, portForwardTitle, p.masterPage().GetRowCount()-1))
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
@ -354,7 +328,6 @@ func showModal(p *ui.Pages, msg, back string, ok func()) {
|
|||
|
||||
func dismissModal(p *ui.Pages, page string) {
|
||||
p.RemovePage(promptPage)
|
||||
p.SwitchToPage(page)
|
||||
}
|
||||
|
||||
func watchFS(ctx context.Context, app *App, dir, file string, cb func()) error {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPortForwardNew(t *testing.T) {
|
||||
po := view.NewPortForward("", "", nil)
|
||||
po.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "PortForwards", po.Name())
|
||||
assert.Equal(t, 15, len(po.Hints()))
|
||||
}
|
||||
|
|
@ -10,15 +10,6 @@ type portSelector struct {
|
|||
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 *App) {
|
||||
f := tview.NewForm()
|
||||
f.SetItemPadding(0)
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
clusterRole roleKind = iota
|
||||
role
|
||||
ClusterRole roleKind = iota
|
||||
Role
|
||||
|
||||
all = "*"
|
||||
rbacTitle = "RBAC"
|
||||
rbacTitle = "Rbac"
|
||||
rbacTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])"
|
||||
)
|
||||
|
||||
|
|
@ -49,15 +49,6 @@ var (
|
|||
"delete",
|
||||
}
|
||||
|
||||
httpVerbs = []string{
|
||||
"get",
|
||||
"post",
|
||||
"put",
|
||||
"patch",
|
||||
"delete",
|
||||
"options",
|
||||
}
|
||||
|
||||
httpTok8sVerbs = map[string]string{
|
||||
"post": "create",
|
||||
"put": "update",
|
||||
|
|
@ -66,8 +57,8 @@ var (
|
|||
|
||||
type roleKind = int8
|
||||
|
||||
// RBAC presents an RBAC policy viewer.
|
||||
type RBAC struct {
|
||||
// Rbac presents an RBAC policy viewer.
|
||||
type Rbac struct {
|
||||
*Table
|
||||
|
||||
app *App
|
||||
|
|
@ -77,24 +68,24 @@ type RBAC struct {
|
|||
cache resource.RowEvents
|
||||
}
|
||||
|
||||
// NewRBAC returns a new viewer.
|
||||
func NewRBAC(app *App, ns, name string, kind roleKind) *RBAC {
|
||||
r := RBAC{
|
||||
// NewRbac returns a new viewer.
|
||||
func NewRbac(app *App, ns, name string, kind roleKind) *Rbac {
|
||||
r := Rbac{
|
||||
app: app,
|
||||
roleName: name,
|
||||
roleType: kind,
|
||||
}
|
||||
r.Table = NewTable(r.getTitle())
|
||||
r.SetActiveNS(ns)
|
||||
r.SetColorerFn(rbacColorer)
|
||||
r.bindKeys()
|
||||
|
||||
return &r
|
||||
}
|
||||
|
||||
// Init initializes the view.
|
||||
func (r *RBAC) Init(ctx context.Context) {
|
||||
func (r *Rbac) Init(ctx context.Context) {
|
||||
r.SetActiveNS(r.app.Config.ActiveNamespace())
|
||||
r.SetColorerFn(rbacColorer)
|
||||
r.Table.Init(ctx)
|
||||
r.bindKeys()
|
||||
|
||||
r.Start()
|
||||
r.SetSortCol(1, len(rbacHeader), true)
|
||||
|
|
@ -102,7 +93,11 @@ func (r *RBAC) Init(ctx context.Context) {
|
|||
}
|
||||
|
||||
// Start watches for viewer updates
|
||||
func (r *RBAC) Start() {
|
||||
func (r *Rbac) Start() {
|
||||
if r.app.Conn() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
r.Stop()
|
||||
|
||||
var ctx context.Context
|
||||
|
|
@ -123,33 +118,35 @@ func (r *RBAC) Start() {
|
|||
}
|
||||
|
||||
// Stop terminates the viewer updater.
|
||||
func (r *RBAC) Stop() {
|
||||
func (r *Rbac) Stop() {
|
||||
if r.cancelFn != nil {
|
||||
r.cancelFn()
|
||||
}
|
||||
}
|
||||
|
||||
// Name returns the component name.
|
||||
func (r *RBAC) Name() string {
|
||||
func (r *Rbac) Name() string {
|
||||
return rbacTitle
|
||||
}
|
||||
|
||||
func (r *RBAC) bindKeys() {
|
||||
func (r *Rbac) bindKeys() {
|
||||
r.RmAction(ui.KeyShiftA)
|
||||
|
||||
r.AddActions(ui.KeyActions{
|
||||
tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false),
|
||||
ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false),
|
||||
ui.KeyP: ui.NewKeyAction("Previous", r.app.PrevCmd, false),
|
||||
ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.SortColCmd(1), false),
|
||||
})
|
||||
}
|
||||
|
||||
func (r *RBAC) getTitle() string {
|
||||
func (r *Rbac) getTitle() string {
|
||||
return skinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.roleName), r.app.Styles.Frame())
|
||||
}
|
||||
|
||||
func (r *RBAC) refresh() {
|
||||
func (r *Rbac) refresh() {
|
||||
if r.app.Conn() == nil {
|
||||
return
|
||||
}
|
||||
data, err := r.reconcile(r.ActiveNS(), r.roleName, r.roleType)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType)
|
||||
|
|
@ -158,7 +155,8 @@ func (r *RBAC) refresh() {
|
|||
r.Update(data)
|
||||
}
|
||||
|
||||
func (r *RBAC) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msgf("!!!YO!!!!")
|
||||
if !r.SearchBuff().Empty() {
|
||||
r.SearchBuff().Reset()
|
||||
return nil
|
||||
|
|
@ -167,7 +165,8 @@ func (r *RBAC) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return r.backCmd(evt)
|
||||
}
|
||||
|
||||
func (r *RBAC) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msgf("!!!!RBAC back!!!")
|
||||
if r.cancelFn != nil {
|
||||
r.cancelFn()
|
||||
}
|
||||
|
|
@ -180,7 +179,7 @@ func (r *RBAC) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return r.app.PrevCmd(evt)
|
||||
}
|
||||
|
||||
func (r *RBAC) reconcile(ns, name string, kind roleKind) (resource.TableData, error) {
|
||||
func (r *Rbac) reconcile(ns, name string, kind roleKind) (resource.TableData, error) {
|
||||
var table resource.TableData
|
||||
|
||||
evts, err := r.rowEvents(ns, name, kind)
|
||||
|
|
@ -191,28 +190,28 @@ func (r *RBAC) reconcile(ns, name string, kind roleKind) (resource.TableData, er
|
|||
return buildTable(r, evts), nil
|
||||
}
|
||||
|
||||
func (r *RBAC) header() resource.Row {
|
||||
func (r *Rbac) header() resource.Row {
|
||||
return rbacHeader
|
||||
}
|
||||
|
||||
func (r *RBAC) getCache() resource.RowEvents {
|
||||
func (r *Rbac) getCache() resource.RowEvents {
|
||||
return r.cache
|
||||
}
|
||||
|
||||
func (r *RBAC) setCache(evts resource.RowEvents) {
|
||||
func (r *Rbac) setCache(evts resource.RowEvents) {
|
||||
r.cache = evts
|
||||
}
|
||||
|
||||
func (r *RBAC) rowEvents(ns, name string, kind roleKind) (resource.RowEvents, error) {
|
||||
func (r *Rbac) rowEvents(ns, name string, kind roleKind) (resource.RowEvents, error) {
|
||||
var (
|
||||
evts resource.RowEvents
|
||||
err error
|
||||
)
|
||||
|
||||
switch kind {
|
||||
case clusterRole:
|
||||
case ClusterRole:
|
||||
evts, err = r.clusterPolicies(name)
|
||||
case role:
|
||||
case Role:
|
||||
evts, err = r.namespacedPolicies(name)
|
||||
default:
|
||||
return evts, fmt.Errorf("Expecting clusterrole/role but found %d", kind)
|
||||
|
|
@ -225,7 +224,7 @@ func (r *RBAC) rowEvents(ns, name string, kind roleKind) (resource.RowEvents, er
|
|||
return evts, nil
|
||||
}
|
||||
|
||||
func (r *RBAC) clusterPolicies(name string) (resource.RowEvents, error) {
|
||||
func (r *Rbac) clusterPolicies(name string) (resource.RowEvents, error) {
|
||||
cr, err := r.app.Conn().DialOrDie().RbacV1().ClusterRoles().Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -234,7 +233,7 @@ func (r *RBAC) clusterPolicies(name string) (resource.RowEvents, error) {
|
|||
return r.parseRules(cr.Rules), nil
|
||||
}
|
||||
|
||||
func (r *RBAC) namespacedPolicies(path string) (resource.RowEvents, error) {
|
||||
func (r *Rbac) namespacedPolicies(path string) (resource.RowEvents, error) {
|
||||
ns, na := namespaced(path)
|
||||
cr, err := r.app.Conn().DialOrDie().RbacV1().Roles(ns).Get(na, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
|
|
@ -244,7 +243,7 @@ func (r *RBAC) namespacedPolicies(path string) (resource.RowEvents, error) {
|
|||
return r.parseRules(cr.Rules), nil
|
||||
}
|
||||
|
||||
func (r *RBAC) parseRules(rules []rbacv1.PolicyRule) resource.RowEvents {
|
||||
func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) resource.RowEvents {
|
||||
m := make(resource.RowEvents, len(rules))
|
||||
for _, r := range rules {
|
||||
for _, grp := range r.APIGroups {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/stretchr/testify/assert"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
func TestHasVerb(t *testing.T) {
|
||||
uu := []struct {
|
||||
vv []string
|
||||
v string
|
||||
e bool
|
||||
}{
|
||||
{[]string{"*"}, "get", true},
|
||||
{[]string{"get", "list", "watch"}, "watch", true},
|
||||
{[]string{"get", "dope", "list"}, "watch", false},
|
||||
{[]string{"get"}, "get", true},
|
||||
{[]string{"post"}, "create", true},
|
||||
{[]string{"put"}, "update", true},
|
||||
{[]string{"list", "deletecollection"}, "deletecollection", true},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, hasVerb(u.vv, u.v))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsVerbs(t *testing.T) {
|
||||
ok, nok := toVerbIcon(true), toVerbIcon(false)
|
||||
|
||||
uu := []struct {
|
||||
vv []string
|
||||
e resource.Row
|
||||
}{
|
||||
{[]string{"*"}, resource.Row{ok, ok, ok, ok, ok, ok, ok, ok, ""}},
|
||||
{[]string{"get", "list", "patch"}, resource.Row{ok, ok, nok, nok, nok, ok, nok, nok, ""}},
|
||||
{[]string{"get", "list", "deletecollection", "post"}, resource.Row{ok, ok, ok, nok, ok, nok, nok, nok, ""}},
|
||||
{[]string{"get", "list", "blee"}, resource.Row{ok, ok, nok, nok, nok, nok, nok, nok, "blee"}},
|
||||
}
|
||||
|
||||
for _, u := range uu {
|
||||
assert.Equal(t, u.e, asVerbs(u.vv...))
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseRules(t *testing.T) {
|
||||
ok, nok := toVerbIcon(true), toVerbIcon(false)
|
||||
_ = nok
|
||||
|
||||
uu := []struct {
|
||||
pp []rbacv1.PolicyRule
|
||||
e map[string]resource.Row
|
||||
}{
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"*"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*.*": {"*.*", "*", ok, ok, ok, ok, ok, ok, ok, ok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{"*"}, Resources: []string{"*"}, Verbs: []string{"get"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*.*": {"*.*", "*", ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{""}, Resources: []string{"*"}, Verbs: []string{"list"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"*": {"*", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{""}, Resources: []string{"pods"}, Verbs: []string{"list"}, ResourceNames: []string{"fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"pods": {"pods", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
"pods/fred": {"pods/fred", "v1", nok, ok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"/fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"/fred": {"/fred", resource.NAValue, ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
{
|
||||
[]rbacv1.PolicyRule{
|
||||
{APIGroups: []string{}, Resources: []string{}, Verbs: []string{"get"}, NonResourceURLs: []string{"fred"}},
|
||||
},
|
||||
map[string]resource.Row{
|
||||
"/fred": {"/fred", resource.NAValue, ok, nok, nok, nok, nok, nok, nok, nok, ""},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var v Rbac
|
||||
for _, u := range uu {
|
||||
evts := v.parseRules(u.pp)
|
||||
for k, v := range u.e {
|
||||
assert.Equal(t, v, evts[k].Fields)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,22 @@
|
|||
package view
|
||||
package view_test
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
// "github.com/derailed/k9s/internal/resource"
|
||||
// "github.com/stretchr/testify/assert"
|
||||
// rbacv1 "k8s.io/api/rbac/v1"
|
||||
// )
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRbacNew(t *testing.T) {
|
||||
cfg := config.NewConfig(ks{})
|
||||
app := view.NewApp(cfg)
|
||||
v := view.NewRbac(app, "", "fred", view.ClusterRole)
|
||||
v.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "Rbac", v.Name())
|
||||
assert.Equal(t, 10, len(v.Hints()))
|
||||
}
|
||||
|
||||
// func TestHasVerb(t *testing.T) {
|
||||
// uu := []struct {
|
||||
|
|
@ -105,7 +115,7 @@ package view
|
|||
// },
|
||||
// }
|
||||
|
||||
// var v rbacView
|
||||
// var v view.Rbac
|
||||
// for _, u := range uu {
|
||||
// evts := v.parseRules(u.pp)
|
||||
// for k, v := range u.e {
|
||||
|
|
|
|||
|
|
@ -78,11 +78,11 @@ func allCRDs(c k8s.Connection, vv viewers) {
|
|||
}
|
||||
|
||||
func showRBAC(app *App, ns, resource, selection string) {
|
||||
kind := clusterRole
|
||||
kind := ClusterRole
|
||||
if resource == "role" {
|
||||
kind = role
|
||||
kind = Role
|
||||
}
|
||||
app.inject(NewRBAC(app, ns, selection, kind))
|
||||
app.inject(NewRbac(app, ns, selection, kind))
|
||||
}
|
||||
|
||||
func showCRD(app *App, ns, resource, selection string) {
|
||||
|
|
@ -98,7 +98,7 @@ func showClusterRole(app *App, ns, resource, selection string) {
|
|||
app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", selection)
|
||||
return
|
||||
}
|
||||
app.inject(NewRBAC(app, ns, crb.RoleRef.Name, clusterRole))
|
||||
app.inject(NewRbac(app, ns, crb.RoleRef.Name, ClusterRole))
|
||||
}
|
||||
|
||||
func showRole(app *App, _, resource, selection string) {
|
||||
|
|
@ -108,7 +108,7 @@ func showRole(app *App, _, resource, selection string) {
|
|||
app.Flash().Errf("Unable to retrieve rolebindings for %s", selection)
|
||||
return
|
||||
}
|
||||
app.inject(NewRBAC(app, ns, fqn(ns, rb.RoleRef.Name), role))
|
||||
app.inject(NewRbac(app, ns, fqn(ns, rb.RoleRef.Name), Role))
|
||||
}
|
||||
|
||||
func showSAPolicy(app *App, _, _, selection string) {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/model"
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/k9s/internal/ui/dialog"
|
||||
|
|
@ -37,7 +36,7 @@ type Resource struct {
|
|||
// NewResource returns a new viewer.
|
||||
func NewResource(title, gvr string, list resource.List) *Resource {
|
||||
return &Resource{
|
||||
MasterDetail: NewMasterDetail(title),
|
||||
MasterDetail: NewMasterDetail(title, list.GetNamespace()),
|
||||
list: list,
|
||||
gvr: gvr,
|
||||
}
|
||||
|
|
@ -49,19 +48,23 @@ func (r *Resource) Init(ctx context.Context) {
|
|||
r.envFn = r.defaultK9sEnv
|
||||
|
||||
table := r.masterPage()
|
||||
table.setFilterFn(r.filterResource)
|
||||
colorer := ui.DefaultColorer
|
||||
if r.colorerFn != nil {
|
||||
colorer = r.colorerFn
|
||||
{
|
||||
table.setFilterFn(r.filterResource)
|
||||
colorer := ui.DefaultColorer
|
||||
if r.colorerFn != nil {
|
||||
colorer = r.colorerFn
|
||||
}
|
||||
table.SetColorerFn(colorer)
|
||||
}
|
||||
table.SetColorerFn(colorer)
|
||||
row, _ := table.GetSelection()
|
||||
if row == 0 && table.GetRowCount() > 0 {
|
||||
table.Select(1, 0)
|
||||
}
|
||||
r.DumpPages()
|
||||
|
||||
r.refresh()
|
||||
{
|
||||
row, _ := table.GetSelection()
|
||||
if row == 0 && table.GetRowCount() > 0 {
|
||||
table.Select(1, 0)
|
||||
}
|
||||
}
|
||||
log.Debug().Msgf("<<<< RESOURCE INIT")
|
||||
}
|
||||
|
||||
// Start initializes updates.
|
||||
|
|
@ -84,18 +87,6 @@ func (r *Resource) Name() string {
|
|||
return r.list.GetName()
|
||||
}
|
||||
|
||||
// Hints returns the current viewer hints
|
||||
func (r *Resource) Hints() model.MenuHints {
|
||||
if r.CurrentPage() == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := r.CurrentPage().Item.(model.Hinter); ok {
|
||||
return c.Hints()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resource) setColorerFn(f ui.ColorerFunc) {
|
||||
r.colorerFn = f
|
||||
}
|
||||
|
|
@ -131,19 +122,6 @@ func (r *Resource) backCmd(*tcell.EventKey) *tcell.EventKey {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *Resource) switchPage1(p string) {
|
||||
log.Debug().Msgf("Switching page to %s", p)
|
||||
if _, ok := r.CurrentPage().Item.(*Table); ok {
|
||||
r.Stop()
|
||||
}
|
||||
|
||||
r.SwitchToPage(p)
|
||||
|
||||
if _, ok := r.CurrentPage().Item.(*Table); ok {
|
||||
r.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Actions...
|
||||
|
||||
|
|
@ -210,9 +188,7 @@ func (r *Resource) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
}
|
||||
r.refresh()
|
||||
}, func() {
|
||||
r.Pop()
|
||||
})
|
||||
}, func() {})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +259,7 @@ func (r *Resource) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
details.SetTextColor(r.app.Styles.FgColor())
|
||||
details.SetText(colorizeYAML(r.app.Styles.Views().Yaml, raw))
|
||||
details.ScrollToBeginning()
|
||||
r.app.Content.Push(details)
|
||||
r.showDetails()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -313,6 +289,7 @@ func (r *Resource) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
|
||||
func (r *Resource) setNamespace(ns string) {
|
||||
if r.list.Namespaced() {
|
||||
r.currentNS = ns
|
||||
r.list.SetNamespace(ns)
|
||||
}
|
||||
}
|
||||
|
|
@ -335,32 +312,34 @@ func (r *Resource) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
r.masterPage().SelectRow(1, true)
|
||||
r.app.CmdBuff().Reset()
|
||||
if err := r.app.Config.SetActiveNamespace(r.currentNS); err != nil {
|
||||
log.Error().Err(err).Msg("Config save NS failed!")
|
||||
}
|
||||
if err := r.app.Config.Save(); err != nil {
|
||||
log.Error().Err(err).Msg("Config save failed!")
|
||||
}
|
||||
r.app.Config.Save()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Resource) refresh() {
|
||||
if r.CurrentPage() == nil {
|
||||
return
|
||||
}
|
||||
if _, ok := r.CurrentPage().Item.(*Table); !ok {
|
||||
if _, ok := r.Top().(*Table); !ok {
|
||||
return
|
||||
}
|
||||
|
||||
r.refreshActions()
|
||||
if r.list.Namespaced() {
|
||||
r.list.SetNamespace(r.currentNS)
|
||||
}
|
||||
if err := r.list.Reconcile(r.app.informer, r.path); err != nil {
|
||||
r.app.Flash().Err(err)
|
||||
|
||||
if r.app.Conn() != nil {
|
||||
if err := r.list.Reconcile(r.app.informer, r.path); err != nil {
|
||||
r.app.Flash().Err(err)
|
||||
}
|
||||
}
|
||||
data := r.list.Data()
|
||||
if r.decorateFn != nil {
|
||||
data = r.decorateFn(data)
|
||||
}
|
||||
r.refreshActions()
|
||||
r.masterPage().Update(data)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ func (r *RestartableResource) restartCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
} else {
|
||||
r.app.Flash().Infof("Rollout restart in progress for `%s...", sel)
|
||||
}
|
||||
}, func() {
|
||||
r.showMaster()
|
||||
})
|
||||
}, func() {})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,17 +41,7 @@ func (r *ReplicaSet) extraActions(aa ui.KeyActions) {
|
|||
aa[tcell.KeyCtrlB] = ui.NewKeyAction("Rollback", r.rollbackCmd, true)
|
||||
}
|
||||
|
||||
func (r *ReplicaSet) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
t := r.masterPage()
|
||||
t.SetSortCol(t.NameColIndex()+col, 0, asc)
|
||||
t.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReplicaSet) showPods(app *App, ns, res, sel string) {
|
||||
func (r *ReplicaSet) showPods(app *App, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
s, err := k8s.NewReplicaSet(app.Conn()).Get(ns, n)
|
||||
if err != nil {
|
||||
|
|
@ -174,7 +164,7 @@ func rollback(Conn k8s.Connection, selectedItem string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
rb, err := polymorphichelpers.RollbackerFor(schema.GroupKind{apiGroup, kind}, Conn.DialOrDie())
|
||||
rb, err := polymorphichelpers.RollbackerFor(schema.GroupKind{Group: apiGroup, Kind: kind}, Conn.DialOrDie())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,24 +35,27 @@ type ScreenDump struct {
|
|||
app *App
|
||||
}
|
||||
|
||||
func NewScreenDump(title, _ string, _ resource.List) ResourceViewer {
|
||||
func NewScreenDump(_, _ string, _ resource.List) ResourceViewer {
|
||||
return &ScreenDump{
|
||||
MasterDetail: NewMasterDetail(title),
|
||||
MasterDetail: NewMasterDetail(dumpTitle, ""),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (s *ScreenDump) Init(ctx context.Context) {
|
||||
s.app = ctx.Value(ui.KeyApp).(*App)
|
||||
s.MasterDetail.Init(ctx)
|
||||
s.registerActions()
|
||||
|
||||
table := s.masterPage()
|
||||
table.SetBorderFocusColor(tcell.ColorSteelBlue)
|
||||
table.SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone)
|
||||
table.SetColorerFn(dumpColorer)
|
||||
table.SetActiveNS(resource.AllNamespaces)
|
||||
table.SetSortCol(table.NameColIndex()+1, 0, true)
|
||||
table.SelectRow(1, true)
|
||||
|
||||
{
|
||||
table.SetBorderFocusColor(tcell.ColorSteelBlue)
|
||||
table.SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone)
|
||||
table.SetColorerFn(dumpColorer)
|
||||
table.SetActiveNS(resource.AllNamespaces)
|
||||
table.SetSortCol(table.NameColIndex(), 0, true)
|
||||
table.SelectRow(1, true)
|
||||
}
|
||||
s.Start()
|
||||
s.refresh()
|
||||
}
|
||||
|
|
@ -90,28 +93,18 @@ func (s *ScreenDump) refresh() {
|
|||
}
|
||||
|
||||
func (s *ScreenDump) registerActions() {
|
||||
aa := ui.KeyActions{
|
||||
ui.KeyP: ui.NewKeyAction("Previous", s.app.PrevCmd, false),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Enter", s.enterCmd, true),
|
||||
s.masterPage().AddActions(ui.KeyActions{
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", s.app.PrevCmd, false),
|
||||
tcell.KeyEnter: ui.NewKeyAction("View", s.enterCmd, true),
|
||||
tcell.KeyCtrlD: ui.NewKeyAction("Delete", s.deleteCmd, true),
|
||||
tcell.KeyCtrlS: ui.NewKeyAction("Save", noopCmd, false),
|
||||
}
|
||||
s.masterPage().AddActions(aa)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ScreenDump) getTitle() string {
|
||||
return dumpTitle
|
||||
}
|
||||
|
||||
func (s *ScreenDump) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
tv := s.masterPage()
|
||||
tv.SetSortCol(tv.NameColIndex()+col, 0, asc)
|
||||
tv.Refresh()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScreenDump) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msg("Dump enter!")
|
||||
tv := s.masterPage()
|
||||
|
|
@ -160,7 +153,14 @@ func (s *ScreenDump) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (s *ScreenDump) Hints() model.MenuHints {
|
||||
return s.Hints()
|
||||
if s.CurrentPage() == nil {
|
||||
return nil
|
||||
}
|
||||
if c, ok := s.CurrentPage().Item.(model.Hinter); ok {
|
||||
return c.Hints()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ScreenDump) hydrate() resource.TableData {
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScreenDumpNew(t *testing.T) {
|
||||
po := view.NewScreenDump("fred", "blee", nil)
|
||||
po.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "Screen Dumps", po.Name())
|
||||
assert.Equal(t, 11, len(po.Hints()))
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
||||
// AutoScrollIndicator represents a log autoscroll status indicator.
|
||||
type AutoScrollIndicator struct {
|
||||
*tview.TextView
|
||||
|
||||
styles *config.Styles
|
||||
scrollStatus int32
|
||||
}
|
||||
|
||||
// NewAutoScrollIndicator returns a new indicator.
|
||||
func NewAutoScrollIndicator(styles *config.Styles) *AutoScrollIndicator {
|
||||
a := AutoScrollIndicator{
|
||||
styles: styles,
|
||||
TextView: tview.NewTextView(),
|
||||
scrollStatus: 1,
|
||||
}
|
||||
a.SetBackgroundColor(config.AsColor(styles.Views().Log.BgColor))
|
||||
a.SetTextAlign(tview.AlignRight)
|
||||
a.SetDynamicColors(true)
|
||||
|
||||
return &a
|
||||
}
|
||||
|
||||
func (a *AutoScrollIndicator) AutoScroll() bool {
|
||||
return atomic.LoadInt32(&a.scrollStatus) == 1
|
||||
}
|
||||
|
||||
func (a *AutoScrollIndicator) ToggleAutoScroll() {
|
||||
var val int32 = 1
|
||||
if a.AutoScroll() {
|
||||
val = 0
|
||||
}
|
||||
atomic.StoreInt32(&a.scrollStatus, val)
|
||||
}
|
||||
|
||||
func (a *AutoScrollIndicator) Refresh() {
|
||||
autoScroll := "Off"
|
||||
if a.AutoScroll() {
|
||||
autoScroll = "On"
|
||||
}
|
||||
a.update("Autoscroll: " + autoScroll)
|
||||
}
|
||||
|
||||
func (a *AutoScrollIndicator) update(status string) {
|
||||
a.Clear()
|
||||
fg, bg := a.styles.Frame().Crumb.FgColor, a.styles.Frame().Crumb.ActiveColor
|
||||
fmt.Fprintf(a, "[%s:%s:b] %-15s ", fg, bg, status)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestScrollIndicatorRefresg(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := view.NewAutoScrollIndicator(defaults)
|
||||
v.Refresh()
|
||||
|
||||
assert.Equal(t, "[black:orange:b] Autoscroll: On \n", v.GetText(false))
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSecretNew(t *testing.T) {
|
||||
s := view.NewSecret("secrets", "", resource.NewSecretList(nil, ""))
|
||||
s.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "secrets", s.Name())
|
||||
assert.Equal(t, 19, len(s.Hints()))
|
||||
}
|
||||
|
|
@ -13,11 +13,11 @@ import (
|
|||
type selectList struct {
|
||||
*tview.List
|
||||
|
||||
parent loggable
|
||||
parent Loggable
|
||||
actions ui.KeyActions
|
||||
}
|
||||
|
||||
func newSelectList(parent loggable) *selectList {
|
||||
func newSelectList(parent Loggable) *selectList {
|
||||
v := selectList{List: tview.NewList(), actions: ui.KeyActions{}}
|
||||
{
|
||||
v.parent = parent
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/derailed/tview"
|
||||
)
|
||||
|
||||
type statusView struct {
|
||||
*tview.TextView
|
||||
|
||||
styles *config.Styles
|
||||
}
|
||||
|
||||
func newStatusView(styles *config.Styles) *statusView {
|
||||
v := statusView{styles: styles, TextView: tview.NewTextView()}
|
||||
{
|
||||
v.SetBackgroundColor(config.AsColor(styles.Views().Log.BgColor))
|
||||
v.SetTextAlign(tview.AlignRight)
|
||||
v.SetDynamicColors(true)
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
func (v *statusView) update(status []string) {
|
||||
v.Clear()
|
||||
last, bgColor := len(status)-1, v.styles.Frame().Crumb.BgColor
|
||||
for i, c := range status {
|
||||
if i == last {
|
||||
bgColor = v.styles.Frame().Crumb.ActiveColor
|
||||
}
|
||||
fmt.Fprintf(v, "[%s:%s:b] %-15s ", v.styles.Frame().Crumb.FgColor, bgColor, c)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewStatus(t *testing.T) {
|
||||
defaults, _ := config.NewStyles("")
|
||||
v := newStatusView(defaults)
|
||||
v.update([]string{"blee", "duh"})
|
||||
|
||||
assert.Equal(t, "[black:aqua:b] blee [black:orange:b] duh \n", v.GetText(false))
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ func (s *StatefulSet) extraActions(aa ui.KeyActions) {
|
|||
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", s.sortColCmd(2, false), false)
|
||||
}
|
||||
|
||||
func (s *StatefulSet) showPods(app *App, ns, res, sel string) {
|
||||
func (s *StatefulSet) showPods(app *App, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
st, err := k8s.NewStatefulSet(app.Conn()).Get(ns, n)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,46 +0,0 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/derailed/tview"
|
||||
"github.com/gdamore/tcell"
|
||||
)
|
||||
|
||||
type styles struct {
|
||||
color tcell.Color
|
||||
attrs tcell.AttrMask
|
||||
align int
|
||||
}
|
||||
|
||||
func stylesFor(app *App, res string, col int) styles {
|
||||
switch res {
|
||||
case "pod":
|
||||
return podStyles(app, col)
|
||||
default:
|
||||
return defaultStyles(app, col)
|
||||
}
|
||||
}
|
||||
|
||||
func podStyles(app *App, col int) styles {
|
||||
st := styles{
|
||||
color: ui.StdColor,
|
||||
attrs: tcell.AttrReverse,
|
||||
align: tview.AlignLeft,
|
||||
}
|
||||
|
||||
switch col {
|
||||
case 5, 6, 7, 8:
|
||||
st.align = tview.AlignLeft
|
||||
st.color = tcell.ColorGreen
|
||||
}
|
||||
|
||||
return st
|
||||
}
|
||||
|
||||
func defaultStyles(app *App, col int) styles {
|
||||
return styles{
|
||||
color: tcell.ColorRed,
|
||||
attrs: tcell.AttrReverse,
|
||||
align: tview.AlignLeft,
|
||||
}
|
||||
}
|
||||
|
|
@ -96,9 +96,8 @@ func (s *Subject) bindKeys() {
|
|||
|
||||
s.AddActions(ui.KeyActions{
|
||||
tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Reset", s.resetCmd, false),
|
||||
tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false),
|
||||
ui.KeySlash: ui.NewKeyAction("Filter", s.activateCmd, false),
|
||||
ui.KeyP: ui.NewKeyAction("Previous", s.app.PrevCmd, false),
|
||||
ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.SortColCmd(1), false),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package view
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
|
@ -16,6 +17,7 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Service represents a service viewer.
|
||||
type Service struct {
|
||||
*Resource
|
||||
|
||||
|
|
@ -23,15 +25,21 @@ type Service struct {
|
|||
logs *Logs
|
||||
}
|
||||
|
||||
// NewService returns a new viewer.
|
||||
func NewService(title, gvr string, list resource.List) ResourceViewer {
|
||||
s := Service{
|
||||
return &Service{
|
||||
Resource: NewResource(title, gvr, list),
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the viewer.
|
||||
func (s *Service) Init(ctx context.Context) {
|
||||
s.extraActionsFn = s.extraActions
|
||||
s.enterFn = s.showPods
|
||||
s.logs = NewLogs(list.GetName(), &s)
|
||||
s.Resource.Init(ctx)
|
||||
|
||||
return &s
|
||||
s.logs = NewLogs(s.list.GetName(), s)
|
||||
s.logs.Init(ctx)
|
||||
}
|
||||
|
||||
// Protocol...
|
||||
|
|
@ -51,17 +59,7 @@ func (s *Service) extraActions(aa ui.KeyActions) {
|
|||
aa[ui.KeyShiftT] = ui.NewKeyAction("Sort Type", s.sortColCmd(1, false), false)
|
||||
}
|
||||
|
||||
func (s *Service) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||
t := s.masterPage()
|
||||
t.SetSortCol(t.NameColIndex()+col, 0, asc)
|
||||
t.Refresh()
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) showPods(app *App, ns, res, sel string) {
|
||||
func (s *Service) showPods(app *App, _, res, sel string) {
|
||||
ns, n := namespaced(sel)
|
||||
svc, err := k8s.NewService(app.Conn()).Get(ns, n)
|
||||
if err != nil {
|
||||
|
|
@ -79,8 +77,7 @@ func (s *Service) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
return evt
|
||||
}
|
||||
|
||||
l := s.GetPrimitive("logs").(*Logs)
|
||||
l.reload("", s, false)
|
||||
s.logs.reload("", s, false)
|
||||
s.Push(s.logs)
|
||||
|
||||
return nil
|
||||
|
|
@ -177,6 +174,10 @@ func (s *Service) benchCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error {
|
||||
if cfg.HTTP.Host == "" {
|
||||
return fmt.Errorf("Invalid benchmark host %q", cfg.HTTP.Host)
|
||||
}
|
||||
|
||||
var err error
|
||||
base := "http://" + cfg.HTTP.Host + ":" + port + cfg.HTTP.Path
|
||||
if s.bench, err = perf.NewBenchmark(base, cfg); err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
package view_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/derailed/k9s/internal/resource"
|
||||
"github.com/derailed/k9s/internal/view"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestServiceNew(t *testing.T) {
|
||||
s := view.NewService("service", "", resource.NewServiceList(nil, ""))
|
||||
s.Init(makeCtx())
|
||||
|
||||
assert.Equal(t, "svc", s.Name())
|
||||
assert.Equal(t, 22, len(s.Hints()))
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/derailed/k9s/internal/ui"
|
||||
"github.com/gdamore/tcell"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type Table struct {
|
||||
|
|
@ -21,6 +22,8 @@ func NewTable(title string) *Table {
|
|||
}
|
||||
|
||||
func (t *Table) Init(ctx context.Context) {
|
||||
log.Debug().Msgf("VIEW Table INIT %q", t.GetBaseTitle())
|
||||
|
||||
t.app = ctx.Value(ui.KeyApp).(*App)
|
||||
|
||||
ctx = context.WithValue(ctx, ui.KeyStyles, t.app.Styles)
|
||||
|
|
@ -102,12 +105,15 @@ func (t *Table) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (t *Table) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
if !t.SearchBuff().Empty() {
|
||||
t.app.Flash().Info("Clearing filter...")
|
||||
log.Debug().Msgf("Table filter reset!")
|
||||
if t.SearchBuff().Empty() {
|
||||
return evt
|
||||
}
|
||||
|
||||
if ui.IsLabelSelector(t.SearchBuff().String()) {
|
||||
t.filterFn("")
|
||||
}
|
||||
t.app.Flash().Info("Clearing filter...")
|
||||
t.SearchBuff().Reset()
|
||||
t.Refresh()
|
||||
|
||||
|
|
@ -115,6 +121,7 @@ func (t *Table) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|||
}
|
||||
|
||||
func (t *Table) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||
log.Debug().Msgf("Table filter activated!")
|
||||
if t.app.InCmdMode() {
|
||||
return evt
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,13 @@ func saveTable(cluster, name string, data resource.TableData) (string, error) {
|
|||
}
|
||||
|
||||
w := csv.NewWriter(file)
|
||||
w.Write(data.Header)
|
||||
if err := w.Write(data.Header); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, r := range data.Rows {
|
||||
w.Write(r.Fields)
|
||||
if err := w.Write(r.Fields); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
if err := w.Error(); err != nil {
|
||||
|
|
@ -67,53 +71,3 @@ func skinTitle(fmat string, style config.Frame) string {
|
|||
|
||||
return fmat
|
||||
}
|
||||
|
||||
func sortRows(evts resource.RowEvents, sortFn ui.SortFn, sortCol ui.SortColumn, keys []string) {
|
||||
rows := make(resource.Rows, 0, len(evts))
|
||||
for k, r := range evts {
|
||||
rows = append(rows, append(r.Fields, k))
|
||||
}
|
||||
sortFn(rows, sortCol)
|
||||
|
||||
for i, r := range rows {
|
||||
keys[i] = r[len(r)-1]
|
||||
}
|
||||
}
|
||||
|
||||
// func defaultSort(rows resource.Rows, sortCol ui.SortColumn) {
|
||||
// t := rowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
|
||||
// sort.Sort(t)
|
||||
// }
|
||||
|
||||
// func sortAllRows(col ui.SortColumn, rows resource.RowEvents, sortFn ui.SortFn) (resource.Row, map[string]resource.Row) {
|
||||
// keys := make([]string, len(rows))
|
||||
// sortRows(rows, sortFn, col, keys)
|
||||
|
||||
// sec := make(map[string]resource.Row, len(rows))
|
||||
// for _, k := range keys {
|
||||
// grp := rows[k].Fields[col.index]
|
||||
// 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)
|
||||
// }
|
||||
// sort.Sort(groupSorter{prim, col.asc})
|
||||
|
||||
// return prim, sec
|
||||
// }
|
||||
|
||||
// func sortIndicator(col ui.SortColumn, style config.Table, index int, name string) string {
|
||||
// if col.index != index {
|
||||
// return name
|
||||
// }
|
||||
|
||||
// order := descIndicator
|
||||
// if col.asc {
|
||||
// order = ascIndicator
|
||||
// }
|
||||
// return fmt.Sprintf("%s[%s::]%s[::]", name, style.Header.SorterColor, order)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func saveYAML(cluster, name, data string) (string, error) {
|
|||
log.Error().Err(err).Msgf("YAML create %s", path)
|
||||
return "", nil
|
||||
}
|
||||
if _, err := fmt.Fprintf(file, data); err != nil {
|
||||
if _, err := file.Write([]byte(data)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -131,7 +131,6 @@ func (i *Informer) List(res, ns string, opts metav1.ListOptions) (k8s.Collection
|
|||
// Get a resource by name.
|
||||
func (i *Informer) Get(res, fqn string, opts metav1.GetOptions) (interface{}, error) {
|
||||
if i == nil {
|
||||
panic("blee")
|
||||
return nil, errors.New("Invalid Get informer")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue