added search/filter feature + support init container

mine
derailed 2019-02-22 17:36:09 -07:00
parent 2cf5ec8877
commit efb95866b1
39 changed files with 993 additions and 921 deletions

81
go.mod
View File

@ -1,71 +1,48 @@
module github.com/derailed/k9s
// replace github.com/k8sland/tview => /Users/fernand/go_wk/k8sland/src/github.com/k8sland/tview
// replace github.com/derailed/tview => /Users/fernand/go_wk/derailed/src/github.com/derailed/tview
require (
cloud.google.com/go v0.34.0
contrib.go.opencensus.io/exporter/ocagent v0.4.3
github.com/Azure/go-autorest v11.4.0+incompatible
github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8
github.com/davecgh/go-spew v1.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/evanphx/json-patch v4.1.0+incompatible
contrib.go.opencensus.io/exporter/ocagent v0.4.3 // indirect
github.com/Azure/go-autorest v11.4.0+incompatible // indirect
github.com/derailed/tview v0.1.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
github.com/fatih/camelcase v1.0.0 // indirect
github.com/gdamore/encoding v1.0.0
github.com/gdamore/tcell v1.1.0
github.com/gogo/protobuf v1.1.1
github.com/golang/protobuf v1.2.0
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf
github.com/googleapis/gnostic v0.2.0
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7
github.com/grpc-ecosystem/grpc-gateway v1.6.2
github.com/imdario/mergo v0.3.6
github.com/inconshreveable/mousetrap v1.0.0
github.com/json-iterator/go v1.1.5
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/k8sland/tview v0.1.1
github.com/gdamore/tcell v1.1.1
github.com/gogo/protobuf v1.1.1 // indirect
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/googleapis/gnostic v0.2.0 // indirect
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4 // indirect
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/json-iterator/go v1.1.5 // indirect
github.com/kr/pretty v0.1.0 // indirect
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08
github.com/mattn/go-runewidth v0.0.4
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd
github.com/modern-go/reflect2 v1.0.1
github.com/onsi/gomega v1.4.3
github.com/peterbourgon/diskv v2.0.1+incompatible
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81
github.com/pmezard/go-difflib v1.0.0
github.com/sirupsen/logrus v1.2.0
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/sirupsen/logrus v1.3.0
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.3
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.2.2
go.opencensus.io v0.19.0
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd
golang.org/x/net v0.0.0-20181217023233-e147a9138326
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f
golang.org/x/sys v0.0.0-20181218192612-074acd46bca6
golang.org/x/text v0.3.0
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2
google.golang.org/appengine v1.3.0
google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb
google.golang.org/grpc v1.17.0
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect
gopkg.in/inf.v0 v0.9.1
go.opencensus.io v0.19.0 // indirect
golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd // indirect
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190202010724-74b699b93c15
k8s.io/apiextensions-apiserver v0.0.0-20190219174952-73ef971883ab
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467
k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a
k8s.io/client-go v10.0.0+incompatible
k8s.io/klog v0.1.0
k8s.io/klog v0.1.0 // indirect
k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 // indirect
k8s.io/kubernetes v1.13.3
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f
k8s.io/utils v0.0.0-20190212002617-cdba02414f76 // indirect
sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8
sigs.k8s.io/yaml v1.1.0
sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8 // indirect
sigs.k8s.io/yaml v1.1.0 // indirect
)

36
go.sum
View File

@ -13,20 +13,24 @@ github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/derailed/tview v0.1.1 h1:s9rGoufUkZYsIFCfV3M23yHxTKa7Rt6n+QFaOgnirpk=
github.com/derailed/tview v0.1.1/go.mod h1:WRYVfgb2PBMLZ/muaSpOc/4H4fYsOPnHOaGnBoJ+hGE=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell v1.1.0 h1:RbQgl7jukmdqROeNcKps7R2YfDCQbWkOd1BwdXrxfr4=
github.com/gdamore/tcell v1.1.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+mLl1A=
github.com/gdamore/tcell v1.1.1 h1:U73YL+jMem2XfhvaIUfPO6MpJawaG92B2funXVb9qLs=
github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -35,6 +39,7 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@ -42,29 +47,29 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4 h1:wAcVWwS69gs5c6cFkCa/ns/eaL2gC761nF8Ugvd1dGw=
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.6.2 h1:8KyC64BiO8ndiGHY5DlFWWdangUPC9QHPakFRre/Ud0=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k8sland/tview v0.1.1 h1:732F8kcz5EjUAsFTZJ5BkJx3n34+EwiQRuDaegeS2yU=
github.com/k8sland/tview v0.1.1/go.mod h1:PwEtOCvGYNgUA2FQuciQBKB6igksu4GHtq3GY6vOkQo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@ -72,6 +77,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -91,11 +97,9 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q
github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rivo/tview v0.0.0-20190124120153-84fdb36408f3/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/rivo/tview v0.0.0-20190213202703-b373355e9db4/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw=
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
@ -140,6 +144,7 @@ google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+
google.golang.org/api v0.0.0-20181220000619-583d854617af h1:iQMS7JKv/0w/iiWf1M49Cg3dmOkBoBZT5KheqPDpaac=
google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
@ -150,12 +155,16 @@ google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9M
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
@ -164,7 +173,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190202010724-74b699b93c15 h1:AoUGjnJ3PJMFz+Rkp4lx3X+6mPUnY1MESJhbUSGX+pc=
k8s.io/api v0.0.0-20190202010724-74b699b93c15/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apiextensions-apiserver v0.0.0-20190219174952-73ef971883ab/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467 h1:zmz9UYvvXrK/B8EDqFuqreJEaXbIWdzEkNgWrN/Cd3o=
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a h1:MrGQxLLZ09Bl5hYYU9VlKnhY60bpPlYd9yXOPnxkdc0=
@ -173,11 +181,13 @@ k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUr
k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk=
k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 h1:M80qeWaBNOX2Uc4plRHcb6k+3YE5VWMaJXKZo+tX9aU=
k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kubernetes v1.13.3 h1:46t44D87wKtdKFgr/lXM60K8xPrW0wO67Woof3Vsv6E=
k8s.io/kubernetes v1.13.3/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f h1:HyUoIBzks9xTaSnMJ6kv/SSmwaQQccokuiriu2cV0aA=
k8s.io/metrics v0.0.0-20181121073115-d8618695b08f/go.mod h1:a25VAbm3QT3xiVl1jtoF1ueAKQM149UdZ+L93ePfV3M=
k8s.io/utils v0.0.0-20190212002617-cdba02414f76 h1:xj+3UbmNFpFTZmF/snXlV1oYutGHU1VmjfkfH1ugr6A=
k8s.io/utils v0.0.0-20190212002617-cdba02414f76/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8 h1:UBkrbecoQliUCGP3Izc0NRKu877BV6VLT3lUykRJURM=
sigs.k8s.io/structured-merge-diff v0.0.0-20190130003954-e5e029740eb8/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=

View File

@ -6,8 +6,6 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/views"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -82,7 +80,7 @@ func initK9sConfig() {
}
ctx := cfg.CurrentContext
switch{
switch {
case isSet(k8sFlags.Context):
ctx = *k8sFlags.Context
config.Root.K9s.CurrentContext = ctx
@ -131,9 +129,6 @@ func run(cmd *cobra.Command, args []string) {
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, ForceColors: true})
initK9s()
initStyles()
initKeys()
app := views.NewApp()
{
app.Init(version, refreshRate, k8sFlags)
@ -141,52 +136,6 @@ func run(cmd *cobra.Command, args []string) {
}
}
func initKeys() {
tcell.KeyNames[tcell.Key(views.Key0)] = "0"
tcell.KeyNames[tcell.Key(views.Key1)] = "1"
tcell.KeyNames[tcell.Key(views.Key2)] = "2"
tcell.KeyNames[tcell.Key(views.Key3)] = "3"
tcell.KeyNames[tcell.Key(views.Key4)] = "4"
tcell.KeyNames[tcell.Key(views.Key5)] = "5"
tcell.KeyNames[tcell.Key(views.Key6)] = "6"
tcell.KeyNames[tcell.Key(views.Key7)] = "7"
tcell.KeyNames[tcell.Key(views.Key8)] = "8"
tcell.KeyNames[tcell.Key(views.Key9)] = "9"
tcell.KeyNames[tcell.Key(views.KeyA)] = "a"
tcell.KeyNames[tcell.Key(views.KeyB)] = "b"
tcell.KeyNames[tcell.Key(views.KeyC)] = "c"
tcell.KeyNames[tcell.Key(views.KeyD)] = "d"
tcell.KeyNames[tcell.Key(views.KeyE)] = "e"
tcell.KeyNames[tcell.Key(views.KeyF)] = "f"
tcell.KeyNames[tcell.Key(views.KeyG)] = "g"
tcell.KeyNames[tcell.Key(views.KeyH)] = "h"
tcell.KeyNames[tcell.Key(views.KeyI)] = "i"
tcell.KeyNames[tcell.Key(views.KeyJ)] = "j"
tcell.KeyNames[tcell.Key(views.KeyK)] = "k"
tcell.KeyNames[tcell.Key(views.KeyL)] = "l"
tcell.KeyNames[tcell.Key(views.KeyM)] = "m"
tcell.KeyNames[tcell.Key(views.KeyN)] = "n"
tcell.KeyNames[tcell.Key(views.KeyO)] = "o"
tcell.KeyNames[tcell.Key(views.KeyP)] = "p"
tcell.KeyNames[tcell.Key(views.KeyQ)] = "q"
tcell.KeyNames[tcell.Key(views.KeyR)] = "r"
tcell.KeyNames[tcell.Key(views.KeyS)] = "s"
tcell.KeyNames[tcell.Key(views.KeyT)] = "t"
tcell.KeyNames[tcell.Key(views.KeyU)] = "u"
tcell.KeyNames[tcell.Key(views.KeyV)] = "v"
tcell.KeyNames[tcell.Key(views.KeyX)] = "x"
tcell.KeyNames[tcell.Key(views.KeyY)] = "y"
tcell.KeyNames[tcell.Key(views.KeyZ)] = "z"
tcell.KeyNames[tcell.Key(views.KeyHelp)] = "?"
}
func initStyles() {
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
tview.Styles.ContrastBackgroundColor = tcell.ColorBlack
tview.Styles.FocusColor = tcell.ColorLightSkyBlue
tview.Styles.BorderColor = tcell.ColorDodgerBlue
}
func initK8sFlags() {
k8sFlags = genericclioptions.NewConfigFlags(false)
rootCmd.Flags().StringVar(
@ -266,4 +215,4 @@ func initK8sFlags() {
"",
"If present, the namespace scope for this CLI request",
)
}
}

View File

@ -78,6 +78,8 @@ func (c *Config) FavNamespaces() []string {
func (c *Config) SetActiveNamespace(ns string) {
if c.K9s.ActiveCluster() != nil {
c.K9s.ActiveCluster().Namespace.SetActive(ns)
} else {
log.Debug("Doh! no active cluster. unable to set active namespace")
}
}

View File

@ -81,7 +81,7 @@ func (k *K9s) Validate(ks KubeSettings) {
k.Clusters[k.CurrentCluster] = NewCluster()
}
if ns, err := ks.CurrentNamespaceName(); err == nil {
if ns, err := ks.CurrentNamespaceName(); err == nil && len(ns) != 0 {
k.Clusters[k.CurrentCluster].Namespace.Active = ns
}
}

View File

@ -203,12 +203,12 @@ func (c *Config) CurrentNamespaceName() (string, error) {
return "", err
}
if ctx, ok := cfg.Contexts[ctx]; ok {
if ctx, ok := cfg.Contexts[ctx]; ok {
if isSet(&ctx.Namespace) {
return ctx.Namespace, nil
}
}
return defaultNamespace, nil
return "", nil
}
// NamespaceNames fetch all available namespaces on current cluster.

View File

@ -48,13 +48,13 @@ func (*Job) Delete(ns, n string) error {
}
// Containers returns all container names on pod
func (j *Job) Containers(ns, n string) ([]string, error) {
func (j *Job) Containers(ns, n string, includeInit bool) ([]string, error) {
pod, err := j.assocPod(ns, n)
if err != nil {
return nil, err
}
log.Debug("Containers found assoc pod", pod)
return NewPod().(Loggable).Containers(ns, pod)
return NewPod().(Loggable).Containers(ns, pod, includeInit)
}
// Logs fetch container logs for a given pod and container.

View File

@ -12,7 +12,7 @@ type (
// Loggable represents a K8s resource that has containers and can be logged.
Loggable interface {
Res
Containers(ns, n string) ([]string, error)
Containers(ns, n string, includeInit bool) ([]string, error)
Logs(ns, n, co string, lines int64, previous bool) *restclient.Request
}
@ -58,7 +58,7 @@ func (*Pod) Delete(ns, n string) error {
}
// Containers returns all container names on pod
func (*Pod) Containers(ns, n string) ([]string, error) {
func (*Pod) Containers(ns, n string, includeInit bool) ([]string, error) {
opts := metav1.GetOptions{}
cc := []string{}
po, err := conn.dialOrDie().CoreV1().Pods(ns).Get(n, opts)
@ -66,6 +66,11 @@ func (*Pod) Containers(ns, n string) ([]string, error) {
return cc, err
}
if includeInit {
for _, c := range po.Spec.InitContainers {
cc = append(cc, c.Name)
}
}
for _, c := range po.Spec.Containers {
cc = append(cc, c.Name)
}

View File

@ -8,7 +8,7 @@ import (
"strings"
"time"
"github.com/k8sland/tview"
"github.com/derailed/tview"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/apimachinery/pkg/watch"

View File

@ -74,11 +74,13 @@ func (r *Job) Marshal(path string) (string, error) {
return r.marshalObject(jo)
}
func (r *Job) Containers(path string) ([]string, error) {
// Containers fetch all the containers on this job, may include init containers.
func (r *Job) Containers(path string, includeInit bool) ([]string, error) {
ns, n := namespaced(path)
return r.caller.(k8s.Loggable).Containers(ns, n)
return r.caller.(k8s.Loggable).Containers(ns, n, includeInit)
}
// Logs retrieves logs for a given container.
func (r *Job) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) {
req := r.caller.(k8s.Loggable).Logs(ns, n, co, lines, prev)
ctx, cancel := context.WithCancel(context.TODO())

View File

@ -5,7 +5,6 @@ import (
"sort"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/watch"
)
@ -219,7 +218,6 @@ func (l *list) Reconcile() error {
err error
)
log.Debugf("Fetching list for resource `%s` in ns `%s`", l.name, l.namespace)
if items, err = l.api.List(l.namespace); err != nil {
return err
}

View File

@ -17,7 +17,7 @@ const defaultTimeout = 1 * time.Second
type (
// Container represents a resource that encompass multiple containers.
Container interface {
Containers(path string) ([]string, error)
Containers(path string, includeInit bool) ([]string, error)
}
// Tailable represents a resource with tailable logs.
@ -114,9 +114,9 @@ func (r *Pod) Marshal(path string) (string, error) {
}
// Containers lists out all the docker contrainers name contained in a pod.
func (r *Pod) Containers(path string) ([]string, error) {
func (r *Pod) Containers(path string, includeInit bool) ([]string, error) {
ns, po := namespaced(path)
return r.caller.(k8s.Loggable).Containers(ns, po)
return r.caller.(k8s.Loggable).Containers(ns, po, includeInit)
}
// Logs tails a given container logs

96
internal/views/alias.go Normal file
View File

@ -0,0 +1,96 @@
package views
import (
"context"
"fmt"
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
)
const (
aliasTitle = "Aliases"
aliasTitleFmt = " [aqua::b]%s[[aqua::b]%d[aqua::-]][aqua::-] "
)
type aliasView struct {
*tableView
current igniter
}
func newAliasView(app *appView) *aliasView {
v := aliasView{tableView: newTableView(app, aliasTitle, nil)}
{
v.SetSelectedStyle(tcell.ColorWhite, tcell.ColorFuchsia, tcell.AttrNone)
v.colorerFn = aliasColorer
v.current = app.content.GetPrimitive("main").(igniter)
}
v.actions[tcell.KeyEnter] = newKeyAction("Search", v.aliasCmd)
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd)
return &v
}
// Init the view.
func (v *aliasView) init(context.Context, string) {
v.update(v.hydrate())
v.app.SetFocus(v)
v.resetTitle()
}
func (v *aliasView) getTitle() string {
return aliasTitle
}
func (v *aliasView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.cmdBuff.isActive() {
return v.filterCmd(evt)
}
return v.runCmd(evt)
}
func (v *aliasView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
v.app.inject(v.current)
return nil
}
func (v *aliasView) runCmd(evt *tcell.EventKey) *tcell.EventKey {
r, _ := v.GetSelection()
if r > 0 {
v.app.command.run(strings.TrimSpace(v.GetCell(r, 0).Text))
}
return nil
}
func (v *aliasView) hints() hints {
return v.actions.toHints()
}
func (v *aliasView) hydrate() resource.TableData {
cmds := helpCmds()
data := resource.TableData{
Header: resource.Row{"ALIAS", "RESOURCE", "APIGROUP"},
Rows: make(resource.RowEvents, len(cmds)),
Namespace: "",
}
for k := range cmds {
fields := resource.Row{
resource.Pad(k, 30),
resource.Pad(cmds[k].title, 30),
resource.Pad(cmds[k].api, 30),
}
data.Rows[k] = &resource.RowEvent{
Action: resource.New,
Fields: fields,
Deltas: fields,
}
}
return data
}
func (v *aliasView) resetTitle() {
v.SetTitle(fmt.Sprintf(aliasTitleFmt, aliasTitle, v.GetRowCount()-1))
}

View File

@ -3,12 +3,11 @@ package views
import (
"context"
"fmt"
"os"
"time"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
@ -24,6 +23,10 @@ type (
init(ctx context.Context, ns string)
}
keyHandler interface {
keyboard(evt *tcell.EventKey) *tcell.EventKey
}
resourceViewer interface {
igniter
}
@ -44,35 +47,47 @@ type (
cancel context.CancelFunc
cmdBuff *cmdBuff
cmdView *cmdView
actions keyActions
}
)
func init() {
initKeys()
initStyles()
}
// NewApp returns a K9s app instance.
func NewApp() *appView {
var app appView
v := appView{Application: tview.NewApplication()}
{
app = appView{
Application: tview.NewApplication(),
pages: tview.NewPages(),
menuView: newMenuView(),
content: tview.NewPages(),
cmdBuff: newCmdBuff(':'),
cmdView: newCmdView('🐶'),
}
app.command = newCommand(&app)
app.focusChanged = app.changedFocus
app.SetInputCapture(app.keyboard)
v.pages = tview.NewPages()
v.actions = make(keyActions)
v.menuView = newMenuView()
v.content = tview.NewPages()
v.cmdBuff = newCmdBuff(':')
v.cmdView = newCmdView('🐶')
v.command = newCommand(&v)
v.flashView = newFlashView(v.Application, "Initializing...")
v.infoView = newInfoView(&v)
v.focusChanged = v.changedFocus
v.SetInputCapture(v.keyboard)
}
return &app
v.actions[KeyColon] = newKeyAction("Cmd", v.activateCmd)
v.actions[tcell.KeyCtrlR] = newKeyAction("Redraw", v.redrawCmd)
v.actions[KeyQ] = newKeyAction("Quit", v.quitCmd)
v.actions[KeyHelp] = newKeyAction("Help", v.helpCmd)
v.actions[tcell.KeyEscape] = newKeyAction("Exit Cmd", v.deactivateCmd)
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd)
v.actions[tcell.KeyBackspace2] = newKeyAction("Goto", v.eraseCmd)
v.actions[tcell.KeyTab] = newKeyAction("Focus", v.focusCmd)
return &v
}
func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags) {
a.version = v
a.infoView = newInfoView(a)
a.infoView.init()
a.flashView = newFlashView(a.Application, "Initializing...")
a.cmdBuff.addListener(a.cmdView)
header := tview.NewFlex()
@ -114,51 +129,81 @@ func (a *appView) Run() {
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if key == tcell.KeyRune {
switch evt.Rune() {
case 'q':
if !a.cmdMode() {
a.quit(evt)
}
case '?':
if !a.cmdView.inCmdMode() {
a.help(evt)
return evt
}
case a.cmdBuff.hotKey:
a.flash(flashInfo, "Entering command mode...")
log.Debug("K9s entering command mode...")
a.cmdBuff.setActive(true)
a.cmdBuff.clear()
return evt
}
if a.cmdBuff.isActive() {
a.cmdBuff.add(evt.Rune())
}
return evt
key = tcell.Key(evt.Rune())
}
switch evt.Key() {
case tcell.KeyCtrlR:
log.Debug("Refreshing screen...")
a.Draw()
case tcell.KeyEsc:
a.cmdBuff.reset()
case tcell.KeyEnter:
if a.cmdBuff.isActive() && !a.cmdBuff.empty() {
a.command.run(a.cmdBuff.String())
}
a.cmdBuff.setActive(false)
case tcell.KeyBackspace2:
if a.cmdBuff.isActive() {
a.cmdBuff.del()
}
case tcell.KeyTab:
a.nextFocus()
if a, ok := a.actions[key]; ok {
log.Debug(">> AppView handled key: ", tcell.KeyNames[key])
return a.action(evt)
}
return evt
}
func (a *appView) redrawCmd(evt *tcell.EventKey) *tcell.EventKey {
a.Draw()
return evt
}
func (a *appView) focusCmd(evt *tcell.EventKey) *tcell.EventKey {
a.nextFocus()
return evt
}
func (a *appView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.isActive() {
a.cmdBuff.del()
return nil
}
return evt
}
func (a *appView) deactivateCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.isActive() {
a.cmdBuff.reset()
}
return evt
}
func (a *appView) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdBuff.isActive() && !a.cmdBuff.empty() {
a.command.run(a.cmdBuff.String())
a.cmdBuff.reset()
return nil
}
a.cmdBuff.setActive(false)
return evt
}
func (a *appView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
a.flash(flashInfo, "Entering command mode...")
log.Debug("Entering app command mode...")
a.cmdBuff.setActive(true)
a.cmdBuff.clear()
return nil
}
func (a *appView) quitCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdMode() {
return evt
}
a.Stop()
return nil
}
func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.cmdView.inCmdMode() {
return evt
}
a.inject(newAliasView(a))
return nil
}
func (a *appView) noopCmd(*tcell.EventKey) *tcell.EventKey {
return nil
}
func (a *appView) showPage(p string) {
a.pages.SwitchToPage(p)
}
@ -195,18 +240,6 @@ func (a *appView) refresh() {
a.infoView.refresh()
}
func (a *appView) help(*tcell.EventKey) {
a.inject(newHelpView(a))
}
func (a *appView) noop(*tcell.EventKey) {
}
func (a *appView) quit(*tcell.EventKey) {
a.Stop()
os.Exit(0)
}
func (a *appView) flash(level flashLevel, m ...string) {
a.flashView.setMessage(level, m...)
}
@ -260,3 +293,10 @@ func (a *appView) nextFocus() {
}
return
}
func initStyles() {
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
tview.Styles.ContrastBackgroundColor = tcell.ColorBlack
tview.Styles.FocusColor = tcell.ColorLightSkyBlue
tview.Styles.BorderColor = tcell.ColorDodgerBlue
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
"github.com/derailed/tview"
log "github.com/sirupsen/logrus"
)

View File

@ -49,6 +49,10 @@ func (c *cmdBuff) del() {
c.fireChanged()
}
func (c *cmdBuff) wipe() {
c.buff = make([]rune, 0, maxBuff)
}
func (c *cmdBuff) clear() {
c.buff = make([]rune, 0, maxBuff)
c.fireChanged()

View File

@ -29,6 +29,10 @@ func defaultColorer(ns string, r *resource.RowEvent) tcell.Color {
return c
}
func aliasColorer(string, *resource.RowEvent) tcell.Color {
return tcell.ColorFuchsia
}
func podColorer(ns string, r *resource.RowEvent) tcell.Color {
c := defaultColorer(ns, r)
@ -37,7 +41,7 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color {
statusCol = 2
}
switch strings.TrimSpace(r.Fields[statusCol]) {
case "ContainerCreating":
case "ContainerCreating", "PodInitializing":
return addColor
case "Terminating", "Initialized":
return highlightColor

View File

@ -27,9 +27,11 @@ func (c *command) run(cmd string) {
var v igniter
switch cmd {
case "q", "quit":
c.app.quit(nil)
c.app.Stop()
return
case "?", "help", "alias":
c.app.help(nil)
c.app.inject(newAliasView(c.app))
return
default:
if res, ok := cmdMap[cmd]; ok {
v = res.viewFn(res.title, c.app, res.listFn(resource.DefaultNamespace), res.colorerFn)

View File

@ -21,9 +21,9 @@ func newContextView(t string, app *appView, list resource.List, c colorerFn) res
return &v
}
func (v *contextView) useContext(*tcell.EventKey) {
func (v *contextView) useContext(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
ctx := strings.TrimSpace(v.selectedItem)
@ -37,7 +37,7 @@ func (v *contextView) useContext(*tcell.EventKey) {
err := v.list.Resource().(*resource.Context).Switch(ctx)
if err != nil {
v.app.flash(flashWarn, err.Error())
return
return evt
}
config.Root.Reset()
@ -46,10 +46,11 @@ func (v *contextView) useContext(*tcell.EventKey) {
v.app.flash(flashInfo, "Switching context to", ctx)
v.refresh()
if tv, ok := v.GetPrimitive("ctx").(*tableView); ok {
tv.table.Select(0, 0)
tv.Select(0, 0)
}
return nil
}
func (v *contextView) extraActions(aa keyActions) {
aa[KeyU] = keyAction{description: "Use", action: v.useContext}
aa[KeyU] = newKeyAction("Use", v.useContext)
}

View File

@ -20,17 +20,19 @@ func newCronJobView(t string, app *appView, list resource.List, c colorerFn) res
return &v
}
func (v *cronJobView) trigger(*tcell.EventKey) {
func (v *cronJobView) trigger(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
v.app.flash(flashInfo, fmt.Sprintf("Triggering %s %s", v.list.GetName(), v.selectedItem))
if err := v.list.Resource().(resource.Runner).Run(v.selectedItem); err != nil {
v.app.flash(flashErr, "Boom!", err.Error())
return evt
}
return nil
}
func (v *cronJobView) extraActions(aa keyActions) {
aa[tcell.KeyCtrlT] = keyAction{description: "Trigger", action: v.trigger}
aa[tcell.KeyCtrlT] = newKeyAction("Trigger", v.trigger)
}

View File

@ -2,27 +2,59 @@ package views
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
)
const detailFmt = " [aqua::-]%s [fuchsia::b]%s "
const detailsTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])[aqua::-] "
// detailsView display yaml output
// detailsView displays text output.
type detailsView struct {
*tview.TextView
actions keyActions
category string
app *appView
actions keyActions
title string
category string
cmdBuff *cmdBuff
backFn actionHandler
numSelections int
}
func newDetailsView() *detailsView {
v := detailsView{TextView: tview.NewTextView()}
v.TextView.SetDynamicColors(true)
v.TextView.SetBorder(true)
v.SetTitleColor(tcell.ColorAqua)
v.SetInputCapture(v.keyboard)
func newDetailsView(app *appView, backFn actionHandler) *detailsView {
v := detailsView{TextView: tview.NewTextView(), app: app, actions: make(keyActions)}
{
v.backFn = backFn
v.SetScrollable(true)
v.SetWrap(false)
v.SetDynamicColors(true)
v.SetRegions(true)
v.SetBorder(true)
v.SetHighlightColor(tcell.ColorOrange)
v.SetTitleColor(tcell.ColorAqua)
v.SetInputCapture(v.keyboard)
v.cmdBuff = newCmdBuff('/')
{
v.cmdBuff.addListener(app.cmdView)
v.cmdBuff.reset()
}
v.SetChangedFunc(func() {
app.Draw()
})
}
v.actions[KeySlash] = newKeyAction("Search", v.activateCmd)
v.actions[tcell.KeyEnter] = newKeyAction("Search", v.searchCmd)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd)
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.backCmd)
v.actions[tcell.KeyTab] = newKeyAction("Next", v.nextCmd)
v.actions[tcell.KeyBacktab] = newKeyAction("Previous", v.prevCmd)
return &v
}
@ -31,23 +63,120 @@ func (v *detailsView) setCategory(n string) {
}
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if evt.Key() == tcell.KeyRune {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
} else {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
key := evt.Key()
if key == tcell.KeyRune {
if v.cmdBuff.isActive() {
v.cmdBuff.add(evt.Rune())
v.refreshTitle()
return nil
}
key = tcell.Key(evt.Rune())
}
if a, ok := v.actions[key]; ok {
log.Debug(">> DetailsView handled ", tcell.KeyNames[key])
return a.action(evt)
}
log.Debug("Doh! DetailsView got no registered action for key ", tcell.KeyNames[key])
return evt
}
func (v *detailsView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.cmdBuff.empty() {
v.cmdBuff.reset()
v.search(evt)
return nil
}
v.cmdBuff.reset()
if v.backFn != nil {
return v.backFn(evt)
}
return evt
}
func (v *detailsView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.cmdBuff.isActive() {
return evt
}
v.cmdBuff.del()
return nil
}
func (v *detailsView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.app.cmdView.inCmdMode() {
v.cmdBuff.setActive(true)
v.cmdBuff.clear()
return nil
}
return evt
}
func (v *detailsView) searchCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.cmdBuff.isActive() && !v.cmdBuff.empty() {
v.app.flash(flashInfo, fmt.Sprintf("Searching for %s...", v.cmdBuff))
v.search(evt)
highlights := v.GetHighlights()
if len(highlights) > 0 {
v.Highlight()
} else {
v.Highlight("0").ScrollToHighlight()
}
}
v.cmdBuff.setActive(false)
return evt
}
func (v *detailsView) search(evt *tcell.EventKey) {
v.numSelections = 0
v.Highlight()
log.Debug("Searching...", v.cmdBuff, v.numSelections)
v.SetText(v.decorateLines(v.GetText(true), v.cmdBuff.String()))
if v.cmdBuff.empty() {
v.app.flash(flashWarn, "Clearing out search query...")
v.refreshTitle()
return
}
if v.numSelections == 0 {
v.app.flash(flashWarn, "No matches found!")
return
}
v.app.flash(flashWarn, fmt.Sprintf("Found <%d> matches! <tab>/<TAB> for next/previous", v.numSelections))
}
func (v *detailsView) nextCmd(evt *tcell.EventKey) *tcell.EventKey {
highlights := v.GetHighlights()
if len(highlights) == 0 || v.numSelections == 0 {
return evt
}
index, _ := strconv.Atoi(highlights[0])
index = (index + 1) % v.numSelections
if index+1 == v.numSelections {
v.app.flash(flashInfo, "Search hit BOTTOM, continuing at TOP")
}
v.Highlight(strconv.Itoa(index)).ScrollToHighlight()
return nil
}
func (v *detailsView) prevCmd(evt *tcell.EventKey) *tcell.EventKey {
highlights := v.GetHighlights()
if len(highlights) == 0 || v.numSelections == 0 {
return evt
}
index, _ := strconv.Atoi(highlights[0])
index = (index - 1 + v.numSelections) % v.numSelections
if index == 0 {
v.app.flash(flashInfo, "Search hit TOP, continuing at BOTTOM")
}
v.Highlight(strconv.Itoa(index)).ScrollToHighlight()
return nil
}
// SetActions to handle keyboard inputs
func (v *detailsView) setActions(aa keyActions) {
v.actions = aa
for k, a := range aa {
v.actions[k] = a
}
}
// Hints fetch mmemonic and hints
@ -58,6 +187,27 @@ func (v *detailsView) hints() hints {
return nil
}
func (v *detailsView) setTitle(t string) {
v.SetTitle(fmt.Sprintf(detailFmt, t, v.category))
func (v *detailsView) refreshTitle() {
v.setTitle(v.title)
}
func (v *detailsView) setTitle(t string) {
v.title = t
title := fmt.Sprintf(detailsTitleFmt, v.category, t)
if !v.cmdBuff.empty() {
title += fmt.Sprintf(searchFmt, v.cmdBuff.String())
}
v.SetTitle(title)
}
func (v *detailsView) decorateLines(buff, q string) string {
rx := regexp.MustCompile(`(?i)` + q)
lines := strings.Split(buff, "\n")
for i, l := range lines {
if m := rx.FindString(l); len(m) > 0 {
lines[i] = rx.ReplaceAllString(l, fmt.Sprintf(`["%d"]%s[""]`, v.numSelections, m))
v.numSelections++
}
}
return strings.Join(lines, "\n")
}

View File

@ -0,0 +1,20 @@
package views
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestDetailsDecorateLines(t *testing.T) {
buff := `
I love blee
blee is much [blue::]cooler [green::]than foo!
`
exp := `
I love ["0"]blee[""]
["1"]blee[""] is much [blue::]cooler [green::]than foo!
`
v := detailsView{}
assert.Equal(t, exp, v.decorateLines(buff, "blee"))
}

View File

@ -7,12 +7,13 @@ import (
log "github.com/sirupsen/logrus"
)
func run(app *appView, args ...string) {
app.Suspend(func() {
func run(app *appView, args ...string) bool {
return app.Suspend(func() {
if err := execute(args...); err != nil {
log.Error("Command failed:", err, args)
app.flash(flashErr, "Doh! command failed", err.Error())
}
log.Debug("Command Done Running..")
})
}

View File

@ -6,7 +6,7 @@ import (
"time"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
"github.com/derailed/tview"
)
const (

View File

@ -1,110 +0,0 @@
package views
import (
"context"
"sort"
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
)
type helpView struct {
*tview.Pages
app *appView
title string
current igniter
keys keyActions
}
func newHelpView(app *appView) *helpView {
return &helpView{
app: app,
title: "Help",
current: app.content.GetPrimitive("main").(igniter),
Pages: tview.NewPages(),
}
}
// Init the view.
func (v *helpView) init(context.Context, string) {
v.keys = keyActions{
tcell.KeyEscape: keyAction{description: "Back", action: v.back},
}
t := tview.NewTable()
{
t.SetBorder(true)
t.SetTitle(" [::b]Commands Aliases ")
t.SetTitleColor(tcell.ColorAqua)
t.SetBorderColor(tcell.ColorDodgerBlue)
t.SetSelectable(true, false)
t.SetSelectedStyle(tcell.ColorWhite, tcell.ColorFuchsia, tcell.AttrNone)
t.SetInputCapture(v.keyboard)
}
var row int
for c, h := range []string{"ALIAS", "RESOURCE", "APIGROUP"} {
th := tview.NewTableCell(h)
th.SetExpansion(3)
t.SetCell(row, c, th)
}
row++
cmds := helpCmds()
kk := make([]string, 0, len(cmds))
for k := range cmds {
kk = append(kk, k)
}
sort.Strings(kk)
var col int
for _, k := range kk {
tc := tview.NewTableCell(resource.Pad(k, 30))
tc.SetExpansion(2)
t.SetCell(row, col, tc)
col++
tc = tview.NewTableCell(resource.Pad(cmds[k].title, 30))
tc.SetExpansion(2)
t.SetCell(row, col, tc)
col++
tc = tview.NewTableCell(resource.Pad(cmds[k].api, 30))
tc.SetExpansion(2)
t.SetCell(row, col, tc)
col = 0
row++
}
v.AddPage("main", t, true, true)
v.SwitchToPage("main")
v.app.SetFocus(v.CurrentPage().Item)
}
func (v *helpView) getTitle() string {
return v.title
}
func (v *helpView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
switch evt.Key() {
case tcell.KeyEscape:
v.back(evt)
return nil
case tcell.KeyEnter:
t := v.GetPrimitive("main").(*tview.Table)
r, _ := t.GetSelection()
if r > 0 {
v.app.command.run(strings.TrimSpace(t.GetCell(r, 0).Text))
return nil
}
}
return evt
}
func (v *helpView) back(evt *tcell.EventKey) {
v.app.inject(v.current)
}
func (v *helpView) hints() hints {
return v.keys.toHints()
}

View File

@ -3,7 +3,7 @@ package views
import (
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
"github.com/derailed/tview"
log "github.com/sirupsen/logrus"
)

View File

@ -12,7 +12,9 @@ type jobView struct {
func newJobView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := jobView{newResourceView(t, app, list, c).(*resourceView)}
v.extraActionsFn = v.extraActions
{
v.extraActionsFn = v.extraActions
}
v.AddPage("logs", newLogsView(&v), true, false)
v.switchPage("job")
return &v
@ -23,23 +25,31 @@ func newJobView(t string, app *appView, list resource.List, c colorerFn) resourc
func (v *jobView) appView() *appView {
return v.app
}
func (v *jobView) backFn() actionHandler {
return v.backCmd
}
func (v *jobView) getList() resource.List {
return v.list
}
func (v *jobView) getSelection() string {
return v.selectedItem
}
// Handlers...
func (v *jobView) logs(*tcell.EventKey) {
func (v *jobView) logs(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem)
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error(err)
return evt
}
l := v.GetPrimitive("logs").(*logsView)
@ -50,8 +60,9 @@ func (v *jobView) logs(*tcell.EventKey) {
v.switchPage("logs")
l.init()
return nil
}
func (v *jobView) extraActions(aa keyActions) {
aa[KeyL] = newKeyHandler("Logs", v.logs)
aa[KeyL] = newKeyAction("Logs", v.logs)
}

View File

@ -3,29 +3,20 @@ package views
import (
"fmt"
"github.com/k8sland/tview"
)
const (
logTitleFmt = " [aqua::b]Logs %s ([fuchsia::-]container=[fuchsia::b]%s[aqua::b]) "
log "github.com/sirupsen/logrus"
)
type logView struct {
*tview.TextView
*detailsView
}
func newLogView(title string, parent loggable) *logView {
v := logView{TextView: tview.NewTextView()}
log.Debug("LogsView init...")
v := logView{detailsView: newDetailsView(parent.appView(), parent.backFn())}
{
v.SetScrollable(true)
v.SetDynamicColors(true)
v.SetBorder(true)
v.SetBorderPadding(0, 0, 1, 1)
v.SetTitle(fmt.Sprintf(logTitleFmt, parent.getSelection(), title))
v.SetWrap(false)
v.SetChangedFunc(func() {
parent.appView().Draw()
})
v.setCategory("Logs")
v.setTitle(parent.getSelection())
}
return &v
}

View File

@ -8,8 +8,8 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
)
@ -42,8 +42,8 @@ func newLogsView(parent loggable) *logsView {
KeyC: {description: "Clear", action: v.clearLogs},
KeyU: {description: "Top", action: v.top},
KeyD: {description: "Bottom", action: v.bottom},
KeyF: {description: "PageUp", action: v.pageUp},
KeyB: {description: "PageDown", action: v.pageDown},
KeyF: {description: "Up", action: v.pageUp},
KeyB: {description: "Down", action: v.pageDown},
})
v.SetInputCapture(v.keyboard)
@ -57,26 +57,26 @@ func (v *logsView) init() {
}
func (v *logsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if m, ok := v.actions[evt.Key()]; ok {
m.action(evt)
return nil
}
if evt.Key() != tcell.KeyRune {
return evt
}
if m, ok := v.actions[tcell.Key(evt.Rune())]; ok {
if m.action != nil {
m.action(evt)
if kv, ok := v.CurrentPage().Item.(keyHandler); ok {
if kv.keyboard(evt) == nil {
return nil
}
}
if i, err := strconv.Atoi(string(evt.Rune())); err == nil {
if _, ok := numKeys[i]; ok {
v.load(i - 1)
key := evt.Key()
if key == tcell.KeyRune {
if i, err := strconv.Atoi(string(evt.Rune())); err == nil {
if _, ok := numKeys[i]; ok {
v.load(i - 1)
return nil
}
}
key = tcell.Key(evt.Rune())
}
if m, ok := v.actions[key]; ok {
log.Debug(">> LogsView handled ", tcell.KeyNames[key])
return m.action(evt)
}
return evt
}
@ -90,7 +90,7 @@ func (v *logsView) setActions(aa keyActions) {
func (v *logsView) hints() hints {
if len(v.containers) > 1 {
for i, c := range v.containers {
v.actions[tcell.Key(numKeys[i+1])] = keyAction{description: c}
v.actions[tcell.Key(numKeys[i+1])] = newKeyAction(c, nil)
}
}
return v.actions.toHints()
@ -99,7 +99,9 @@ func (v *logsView) hints() hints {
func (v *logsView) addContainer(n string) {
v.containers = append(v.containers, n)
l := newLogView(n, v.parent)
l.SetInputCapture(v.keyboard)
{
l.SetInputCapture(v.keyboard)
}
v.AddPage(n, l, true, false)
}
@ -145,6 +147,7 @@ func (v *logsView) doLoad(path, co string) error {
c := make(chan string)
go func() {
l, count, first := v.CurrentPage().Item.(*logView), 0, true
l.setTitle(path + ":" + co)
for {
select {
case line, ok := <-c:
@ -189,50 +192,55 @@ func (v *logsView) doLoad(path, co string) error {
return err
}
v.cancelFunc = cancelFn
return nil
}
// ----------------------------------------------------------------------------
// Actions...
func (v *logsView) back(*tcell.EventKey) {
func (v *logsView) back(evt *tcell.EventKey) *tcell.EventKey {
v.stop()
v.parent.switchPage(v.parent.getList().GetName())
return nil
}
func (v *logsView) top(*tcell.EventKey) {
func (v *logsView) top(evt *tcell.EventKey) *tcell.EventKey {
if p := v.CurrentPage(); p != nil {
v.parent.appView().flash(flashInfo, "Top logs...")
p.Item.(*logView).ScrollToBeginning()
}
return nil
}
func (v *logsView) bottom(*tcell.EventKey) {
func (v *logsView) bottom(*tcell.EventKey) *tcell.EventKey {
if p := v.CurrentPage(); p != nil {
v.parent.appView().flash(flashInfo, "Bottom logs...")
p.Item.(*logView).ScrollToEnd()
}
return nil
}
func (v *logsView) pageUp(*tcell.EventKey) {
func (v *logsView) pageUp(*tcell.EventKey) *tcell.EventKey {
if p := v.CurrentPage(); p != nil {
v.parent.appView().flash(flashInfo, "Page Up logs...")
p.Item.(*logView).PageUp()
}
return nil
}
func (v *logsView) pageDown(*tcell.EventKey) {
func (v *logsView) pageDown(*tcell.EventKey) *tcell.EventKey {
if p := v.CurrentPage(); p != nil {
v.parent.appView().flash(flashInfo, "Page Down logs...")
p.Item.(*logView).PageDown()
}
return nil
}
func (v *logsView) clearLogs(*tcell.EventKey) {
func (v *logsView) clearLogs(*tcell.EventKey) *tcell.EventKey {
if p := v.CurrentPage(); p != nil {
v.parent.appView().flash(flashInfo, "Clearing logs...")
v.buffer.clear()
p.Item.(*logView).Clear()
}
return nil
}

View File

@ -2,25 +2,27 @@ package views
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
)
const (
menuSepFmt = " [dodgerblue::b]%-8s [white::d]%s "
menuSepFmt = " [dodgerblue::b]%-9s [white::d]%s "
menuIndexFmt = " [fuchsia::b]<%d> [white::d]%s "
maxRows = 6
colLen = 20
)
type (
keyboardHandler func(*tcell.EventKey)
var menuRX = regexp.MustCompile(`\d`)
type (
hint struct {
mnemonic, display string
}
@ -29,37 +31,43 @@ type (
hinter interface {
hints() hints
}
keyAction struct {
description string
action keyboardHandler
}
keyActions map[tcell.Key]keyAction
menuView struct {
*tview.Table
}
)
func (h hints) Len() int {
return len(h)
}
func (h hints) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h hints) Less(i, j int) bool {
n, err1 := strconv.Atoi(h[i].mnemonic)
m, err2 := strconv.Atoi(h[j].mnemonic)
if err1 == nil && err2 == nil {
return n < m
}
d := strings.Compare(h[i].mnemonic, h[j].mnemonic)
return d < 0
if err1 == nil && err2 != nil {
return true
}
if err1 != nil && err2 == nil {
return false
}
return strings.Compare(h[i].mnemonic, h[j].mnemonic) < 0
}
func newKeyHandler(d string, a keyboardHandler) keyAction {
// -----------------------------------------------------------------------------
type (
actionHandler func(*tcell.EventKey) *tcell.EventKey
keyAction struct {
description string
action actionHandler
}
keyActions map[tcell.Key]keyAction
)
func newKeyAction(d string, a actionHandler) keyAction {
return keyAction{description: d, action: a}
}
@ -68,6 +76,11 @@ func newMenuView() *menuView {
return &v
}
// -----------------------------------------------------------------------------
type menuView struct {
*tview.Table
}
func (v *menuView) setMenu(hh hints) {
v.Clear()
sort.Sort(hh)
@ -75,17 +88,12 @@ func (v *menuView) setMenu(hh hints) {
var row, col int
firstNS, firstCmd := true, true
for _, h := range hh {
_, err := strconv.Atoi(h.mnemonic)
if err == nil && firstNS {
row = 0
col = 2
firstNS = false
isDigit := menuRX.MatchString(h.mnemonic)
if isDigit && firstNS {
row, col, firstNS = 0, 2, false
}
if err != nil && firstCmd {
row = 0
col = 0
firstCmd = false
if !isDigit && firstCmd {
row, col, firstCmd = 0, 0, false
}
c := tview.NewTableCell(v.item(h))
v.SetCell(row, col, c)
@ -106,14 +114,25 @@ func (v *menuView) item(h hint) string {
if err == nil {
return fmt.Sprintf(menuIndexFmt, i, resource.Truncate(h.display, 14))
}
return fmt.Sprintf(menuSepFmt, v.toMnemonic(h.mnemonic), h.display)
}
func (a keyActions) skipKey(k tcell.Key) bool {
switch k {
case tcell.KeyBackspace2:
fallthrough
case tcell.KeyEnter:
return true
}
return false
}
func (a keyActions) toHints() hints {
kk := make([]int, 0, len(a))
for k := range a {
kk = append(kk, int(k))
if !a.skipKey(k) {
kk = append(kk, int(k))
}
}
sort.Ints(kk)
@ -123,6 +142,8 @@ func (a keyActions) toHints() hints {
hh = append(hh, hint{
mnemonic: name,
display: a[tcell.Key(k)].description})
} else {
log.Errorf("Unable to local KeyName for %#v", k)
}
}
return hh
@ -173,7 +194,39 @@ const (
KeyX
KeyY
KeyZ
KeyHelp = 63
KeyHelp = 63
KeySlash = 47
KeyColon = 58
)
// Define Shift Keys
const (
KeyShiftA tcell.Key = iota + 65
KeyShiftB
KeyShiftC
KeyShiftD
KeyShiftE
KeyShiftF
KeyShiftG
KeyShiftH
KeyShiftI
KeyShiftJ
KeyShiftK
KeyShiftL
KeyShiftM
KeyShiftN
KeyShiftO
KeyShiftP
KeyShiftQ
KeyShiftR
KeyShiftS
KeyShiftT
KeyShiftU
KeyShiftV
KeyShiftW
KeyShiftX
KeyShiftY
KeyShiftZ
)
var numKeys = map[int]int32{
@ -188,3 +241,71 @@ var numKeys = map[int]int32{
8: Key8,
9: Key9,
}
func initKeys() {
tcell.KeyNames[tcell.Key(Key0)] = "0"
tcell.KeyNames[tcell.Key(Key1)] = "1"
tcell.KeyNames[tcell.Key(Key2)] = "2"
tcell.KeyNames[tcell.Key(Key3)] = "3"
tcell.KeyNames[tcell.Key(Key4)] = "4"
tcell.KeyNames[tcell.Key(Key5)] = "5"
tcell.KeyNames[tcell.Key(Key6)] = "6"
tcell.KeyNames[tcell.Key(Key7)] = "7"
tcell.KeyNames[tcell.Key(Key8)] = "8"
tcell.KeyNames[tcell.Key(Key9)] = "9"
tcell.KeyNames[tcell.Key(KeyA)] = "a"
tcell.KeyNames[tcell.Key(KeyB)] = "b"
tcell.KeyNames[tcell.Key(KeyC)] = "c"
tcell.KeyNames[tcell.Key(KeyD)] = "d"
tcell.KeyNames[tcell.Key(KeyE)] = "e"
tcell.KeyNames[tcell.Key(KeyF)] = "f"
tcell.KeyNames[tcell.Key(KeyG)] = "g"
tcell.KeyNames[tcell.Key(KeyH)] = "h"
tcell.KeyNames[tcell.Key(KeyI)] = "i"
tcell.KeyNames[tcell.Key(KeyJ)] = "j"
tcell.KeyNames[tcell.Key(KeyK)] = "k"
tcell.KeyNames[tcell.Key(KeyL)] = "l"
tcell.KeyNames[tcell.Key(KeyM)] = "m"
tcell.KeyNames[tcell.Key(KeyN)] = "n"
tcell.KeyNames[tcell.Key(KeyO)] = "o"
tcell.KeyNames[tcell.Key(KeyP)] = "p"
tcell.KeyNames[tcell.Key(KeyQ)] = "q"
tcell.KeyNames[tcell.Key(KeyR)] = "r"
tcell.KeyNames[tcell.Key(KeyS)] = "s"
tcell.KeyNames[tcell.Key(KeyT)] = "t"
tcell.KeyNames[tcell.Key(KeyU)] = "u"
tcell.KeyNames[tcell.Key(KeyV)] = "v"
tcell.KeyNames[tcell.Key(KeyX)] = "x"
tcell.KeyNames[tcell.Key(KeyY)] = "y"
tcell.KeyNames[tcell.Key(KeyZ)] = "z"
tcell.KeyNames[tcell.Key(KeyShiftA)] = "SHIFT-A"
tcell.KeyNames[tcell.Key(KeyShiftB)] = "SHIFT-B"
tcell.KeyNames[tcell.Key(KeyShiftC)] = "SHIFT-C"
tcell.KeyNames[tcell.Key(KeyShiftD)] = "SHIFT-D"
tcell.KeyNames[tcell.Key(KeyShiftE)] = "SHIFT-E"
tcell.KeyNames[tcell.Key(KeyShiftF)] = "SHIFT-F"
tcell.KeyNames[tcell.Key(KeyShiftG)] = "SHIFT-G"
tcell.KeyNames[tcell.Key(KeyShiftH)] = "SHIFT-H"
tcell.KeyNames[tcell.Key(KeyShiftI)] = "SHIFT-I"
tcell.KeyNames[tcell.Key(KeyShiftJ)] = "SHIFT-J"
tcell.KeyNames[tcell.Key(KeyShiftK)] = "SHIFT-K"
tcell.KeyNames[tcell.Key(KeyShiftL)] = "SHIFT-L"
tcell.KeyNames[tcell.Key(KeyShiftM)] = "SHIFT-M"
tcell.KeyNames[tcell.Key(KeyShiftN)] = "SHIFT-N"
tcell.KeyNames[tcell.Key(KeyShiftO)] = "SHIFT-O"
tcell.KeyNames[tcell.Key(KeyShiftP)] = "SHIFT-P"
tcell.KeyNames[tcell.Key(KeyShiftQ)] = "SHIFT-Q"
tcell.KeyNames[tcell.Key(KeyShiftR)] = "SHIFT-R"
tcell.KeyNames[tcell.Key(KeyShiftS)] = "SHIFT-S"
tcell.KeyNames[tcell.Key(KeyShiftT)] = "SHIFT-T"
tcell.KeyNames[tcell.Key(KeyShiftU)] = "SHIFT-U"
tcell.KeyNames[tcell.Key(KeyShiftV)] = "SHIFT-V"
tcell.KeyNames[tcell.Key(KeyShiftX)] = "SHIFT-X"
tcell.KeyNames[tcell.Key(KeyShiftY)] = "SHIFT-Y"
tcell.KeyNames[tcell.Key(KeyShiftZ)] = "SHIFT-Z"
tcell.KeyNames[tcell.Key(KeyHelp)] = "?"
tcell.KeyNames[tcell.Key(KeySlash)] = "/"
}

View File

@ -2,7 +2,7 @@ package views
import (
"fmt"
"strings"
"regexp"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/resource"
@ -15,40 +15,43 @@ const (
deltaNSIndicator = "(𝜟)"
)
var nsCleanser = regexp.MustCompile(`(\w+)[+|(*)|(𝜟)]*`)
type namespaceView struct {
*resourceView
}
func newNamespaceView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := namespaceView{
resourceView: newResourceView(t, app, list, c).(*resourceView),
}
v := namespaceView{newResourceView(t, app, list, c).(*resourceView)}
v.extraActionsFn = v.extraActions
v.selectedFn = v.getSelectedItem
v.decorateDataFn = v.decorate
v.switchPage("ns")
return &v
}
func (v *namespaceView) useNamespace(*tcell.EventKey) {
if !v.rowSelected() {
return
}
ns := v.selectedItem
for _, i := range []string{deltaNSIndicator, favNSIndicator, defaultNSIndicator} {
if strings.HasSuffix(ns, i) {
ns = strings.TrimRight(ns, i)
}
}
v.refresh()
config.Root.SetActiveNamespace(ns)
config.Root.Save()
v.app.flash(flashInfo, fmt.Sprintf("Setting namespace `%s as your default namespace", ns))
func (v *namespaceView) extraActions(aa keyActions) {
aa[KeyU] = newKeyAction("Use", v.useNamespace)
}
func (v *namespaceView) extraActions(aa keyActions) {
aa[KeyU] = keyAction{description: "Use", action: v.useNamespace}
func (v *namespaceView) useNamespace(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
ns := v.getSelectedItem()
config.Root.SetActiveNamespace(ns)
config.Root.Save()
v.refresh()
v.app.flash(flashInfo, fmt.Sprintf("Setting namespace `%s as your default namespace", ns))
return nil
}
func (v *namespaceView) getSelectedItem() string {
return v.cleanser(v.selectedItem)
}
func (*namespaceView) cleanser(s string) string {
return nsCleanser.ReplaceAllString(s, `$1`)
}
func (v *namespaceView) decorate(data resource.TableData) resource.TableData {
@ -59,18 +62,15 @@ func (v *namespaceView) decorate(data resource.TableData) resource.TableData {
Deltas: resource.Row{"", "", ""},
}
}
for k, v := range data.Rows {
if config.InList(config.Root.FavNamespaces(), k) {
v.Fields[0] += "+"
v.Action = resource.Unchanged
}
if config.Root.ActiveNamespace() == k {
v.Fields[0] += "(*)"
v.Action = resource.Unchanged
}
}
return data
}

View File

@ -0,0 +1,27 @@
package views
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNSCleanser(t *testing.T) {
var v namespaceView
uu := []struct {
s, e string
}{
{"fred", "fred"},
{"fred+", "fred"},
{"fred(*)", "fred"},
{"fred+(*)", "fred"},
{"fred-blee+(*)", "fred-blee"},
{"fred1-blee2+(*)", "fred1-blee2"},
{"fred(𝜟)", "fred"},
}
for _, u := range uu {
assert.Equal(t, u.e, v.cleanser(u.s))
}
}

View File

@ -12,6 +12,7 @@ type podView struct {
type loggable interface {
appView() *appView
backFn() actionHandler
getSelection() string
getList() resource.List
switchPage(n string)
@ -19,7 +20,9 @@ type loggable interface {
func newPodView(t string, app *appView, list resource.List, c colorerFn) resourceViewer {
v := podView{newResourceView(t, app, list, c).(*resourceView)}
v.extraActionsFn = v.extraActions
{
v.extraActionsFn = v.extraActions
}
v.AddPage("logs", newLogsView(&v), true, false)
@ -29,7 +32,7 @@ func newPodView(t string, app *appView, list resource.List, c colorerFn) resourc
v.sshInto(v.selectedItem, t)
})
picker.setActions(keyActions{
tcell.KeyEscape: {description: "Back", action: v.back},
tcell.KeyEscape: {description: "Back", action: v.backCmd},
})
v.AddPage("choose", picker, true, false)
}
@ -40,56 +43,60 @@ func newPodView(t string, app *appView, list resource.List, c colorerFn) resourc
// Protocol...
func (v *podView) backFn() actionHandler {
return v.backCmd
}
func (v *podView) appView() *appView {
return v.app
}
func (v *podView) getList() resource.List {
return v.list
}
func (v *podView) getSelection() string {
return v.selectedItem
}
// Handlers...
func (v *podView) logs(*tcell.EventKey) {
func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem)
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error(err)
return evt
}
l := v.GetPrimitive("logs").(*logsView)
l.deleteAllPages()
for _, c := range cc {
l.addContainer(c)
}
v.switchPage("logs")
l.init()
return nil
}
func (v *podView) shell(*tcell.EventKey) {
func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem)
cc, err := fetchContainers(v.list, v.selectedItem, false)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error("Error fetching containers", err)
return
return evt
}
if len(cc) == 1 {
v.sshInto(v.selectedItem, "")
return
return nil
}
v.showPicker(cc)
return
return nil
}
func (v *podView) showPicker(cc []string) {
@ -109,13 +116,13 @@ func (v *podView) sshInto(path, co string) {
}
func (v *podView) extraActions(aa keyActions) {
aa[KeyL] = newKeyHandler("Logs", v.logs)
aa[KeyS] = newKeyHandler("Shell", v.shell)
aa[KeyL] = newKeyAction("Logs", v.logsCmd)
aa[KeyS] = newKeyAction("Shell", v.shellCmd)
}
func fetchContainers(l resource.List, po string) ([]string, error) {
func fetchContainers(l resource.List, po string, includeInit bool) ([]string, error) {
if len(po) == 0 {
return []string{}, nil
}
return l.Resource().(resource.Container).Containers(po)
return l.Resource().(resource.Container).Containers(po, includeInit)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"fmt"
"path"
"regexp"
"strconv"
"strings"
"sync"
@ -13,8 +12,8 @@ import (
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
)
@ -40,6 +39,7 @@ type (
update sync.Mutex
list resource.List
extraActionsFn func(keyActions)
selectedFn func() string
decorateDataFn func(resource.TableData) resource.TableData
}
)
@ -56,36 +56,17 @@ func newResourceView(title string, app *appView, list resource.List, c colorerFn
tv := newTableView(app, v.title, list.SortFn())
{
tv.SetColorer(c)
tv.table.SetSelectionChangedFunc(v.selChanged)
tv.SetSelectionChangedFunc(v.selChanged)
}
v.AddPage(v.list.GetName(), tv, true, true)
var xray details
if list.HasXRay() {
xray = newXrayView(app)
} else {
xray = newYamlView(app)
}
xray.setActions(keyActions{
tcell.KeyEscape: {description: "Back", action: v.back},
})
details := newDetailsView()
details.setActions(keyActions{
tcell.KeyEscape: {description: "Back", action: v.back},
})
details := newDetailsView(app, v.backCmd)
v.AddPage("details", details, true, false)
v.AddPage("xray", xray, true, false)
return &v
}
// Init watches all running pods in given namespace
func (v *resourceView) init(ctx context.Context, ns string) {
details := v.GetPrimitive("xray").(details)
details.clear()
v.selectedItem, v.selectedNS = noSelection, ns
go func(ctx context.Context) {
@ -103,7 +84,7 @@ func (v *resourceView) init(ctx context.Context, ns string) {
}(ctx)
v.refreshActions()
if tv, ok := v.CurrentPage().Item.(*tableView); ok {
tv.table.Select(0, 0)
tv.Select(0, 0)
}
}
@ -119,6 +100,13 @@ func (v *resourceView) colorFn(f colorerFn) {
v.getTV().SetColorer(f)
}
func (v *resourceView) getSelectedItem() string {
if v.selectedFn != nil {
return v.selectedFn()
}
return v.selectedItem
}
// Protocol...
// Hints fetch menu hints
@ -126,89 +114,89 @@ func (v *resourceView) hints() hints {
return v.CurrentPage().Item.(hinter).hints()
}
// ----------------------------------------------------------------------------
// Actions...
func (v *resourceView) back(*tcell.EventKey) {
func (v *resourceView) backCmd(*tcell.EventKey) *tcell.EventKey {
v.switchPage(v.list.GetName())
return nil
}
func (v *resourceView) delete(*tcell.EventKey) {
func (v *resourceView) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
v.getTV().setDeleted()
v.app.flash(flashInfo, fmt.Sprintf("Deleting %s %s", v.list.GetName(), v.selectedItem))
if err := v.list.Resource().Delete(v.selectedItem); err != nil {
sel := v.getSelectedItem()
v.app.flash(flashInfo, fmt.Sprintf("Deleting %s %s", v.list.GetName(), sel))
if err := v.list.Resource().Delete(sel); err != nil {
v.app.flash(flashErr, "Boom!", err.Error())
}
v.selectedItem = noSelection
return nil
}
func (v *resourceView) describe(*tcell.EventKey) {
func (v *resourceView) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
selected := v.selectedItem
selected = strings.Replace(selected, "+", "", -1)
selected = strings.Replace(selected, "(*)", "", -1)
raw, err := v.list.Resource().Describe(v.title, selected)
sel := v.getSelectedItem()
raw, err := v.list.Resource().Describe(v.title, sel)
if err != nil {
v.app.flash(flashErr, "Unable to describe this resource", err.Error())
v.app.flash(flashErr, "Unable to describeCmd this resource", err.Error())
log.Error(err)
return
return evt
}
var re = regexp.MustCompile(`(?m:(^(.+)$))`)
str := re.ReplaceAllString(string(raw), `[aqua]$1`)
details := v.GetPrimitive("details").(*detailsView)
details.ScrollToBeginning()
details.setCategory("DESC")
details.SetText(str)
details.setTitle(selected)
{
details.setCategory("Describe")
details.setTitle(sel)
details.SetTextColor(tcell.ColorAqua)
details.SetText(string(raw))
details.ScrollToBeginning()
}
v.switchPage("details")
return nil
}
func (v *resourceView) view(*tcell.EventKey) {
func (v *resourceView) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
raw, err := v.list.Resource().Marshal(v.selectedItem)
sel := v.getSelectedItem()
raw, err := v.list.Resource().Marshal(sel)
if err != nil {
v.app.flash(flashErr, "Unable to marshal resource", err.Error())
log.Error(err)
return
return evt
}
var re = regexp.MustCompile(`(?m:([\w|\.|"|\-|\/|\@]+):(.*)$)`)
str := re.ReplaceAllString(string(raw), `[aqua]$1: [white]$2`)
details := v.GetPrimitive("details").(*detailsView)
details.ScrollToBeginning()
details.setCategory("YAML")
details.SetText(str)
details.setTitle(v.selectedItem)
{
details.setCategory("View")
details.setTitle(sel)
details.SetTextColor(tcell.ColorMediumAquamarine)
details.SetText(string(raw))
details.ScrollToBeginning()
}
v.switchPage("details")
return nil
}
func (v *resourceView) edit(*tcell.EventKey) {
func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return
return evt
}
v.app.flash(flashInfo, fmt.Sprintf("Editing %s %s", v.title, v.selectedItem))
ns, s := namespaced(v.selectedItem)
run(v.app, "edit", v.list.GetName(), "-n", ns, s)
return
return evt
}
func (v *resourceView) switchNamespace(evt *tcell.EventKey) {
func (v *resourceView) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
i, _ := strconv.Atoi(string(evt.Rune()))
ns := v.namespaces[i]
v.doSwitchNamespace(ns)
return nil
}
func (v *resourceView) doSwitchNamespace(ns string) {
@ -225,7 +213,7 @@ func (v *resourceView) doSwitchNamespace(ns string) {
v.refresh()
v.selectItem(0, 0)
v.getTV().resetTitle()
v.getTV().table.Select(0, 0)
v.getTV().Select(0, 0)
v.app.cmdBuff.reset()
config.Root.SetActiveNamespace(v.selectedNS)
config.Root.Save()
@ -275,16 +263,16 @@ func (v *resourceView) selectItem(r, c int) {
t := v.getTV()
switch v.list.GetNamespace() {
case resource.NotNamespaced:
v.selectedItem = strings.TrimSpace(t.table.GetCell(r, 0).Text)
v.selectedItem = strings.TrimSpace(t.GetCell(r, 0).Text)
case resource.AllNamespaces:
v.selectedItem = path.Join(
strings.TrimSpace(t.table.GetCell(r, 0).Text),
strings.TrimSpace(t.table.GetCell(r, 1).Text),
strings.TrimSpace(t.GetCell(r, 0).Text),
strings.TrimSpace(t.GetCell(r, 1).Text),
)
default:
v.selectedItem = path.Join(
v.selectedNS,
strings.TrimSpace(t.table.GetCell(r, 0).Text),
strings.TrimSpace(t.GetCell(r, 0).Text),
)
}
}
@ -293,8 +281,8 @@ func (v *resourceView) switchPage(p string) {
v.update.Lock()
{
v.SwitchToPage(p)
h := v.GetPrimitive(p).(hinter)
v.selectedNS = v.list.GetNamespace()
h := v.GetPrimitive(p).(hinter)
v.app.setHints(h.hints())
v.app.SetFocus(v.CurrentPage().Item)
}
@ -328,30 +316,30 @@ func (v *resourceView) refreshActions() {
}
}
aa := keyActions{}
aa := make(keyActions)
if v.list.Access(resource.NamespaceAccess) {
v.namespaces = make(map[int]string, config.MaxFavoritesNS)
for i, n := range config.Root.FavNamespaces() {
aa[tcell.Key(numKeys[i])] = newKeyHandler(n, v.switchNamespace)
aa[tcell.Key(numKeys[i])] = newKeyAction(n, v.switchNamespaceCmd)
v.namespaces[i] = n
}
}
if v.list.Access(resource.EditAccess) {
aa[KeyE] = newKeyHandler("Edit", v.edit)
aa[KeyE] = newKeyAction("Edit", v.editCmd)
}
if v.list.Access(resource.DeleteAccess) {
aa[tcell.KeyCtrlD] = newKeyHandler("Delete", v.delete)
aa[tcell.KeyCtrlD] = newKeyAction("Delete", v.deleteCmd)
}
if v.list.Access(resource.ViewAccess) {
aa[KeyV] = newKeyHandler("View", v.view)
aa[KeyV] = newKeyAction("View", v.viewCmd)
}
if v.list.Access(resource.DescribeAccess) {
aa[KeyD] = newKeyHandler("Describe", v.describe)
aa[KeyD] = newKeyAction("Describe", v.describeCmd)
}
aa[KeyHelp] = newKeyHandler("Help", v.app.noop)
aa[KeyHelp] = newKeyAction("Help", v.app.noopCmd)
if v.extraActionsFn != nil {
v.extraActionsFn(aa)

View File

@ -3,8 +3,8 @@ package views
import (
"strconv"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
)
type selectList struct {
@ -15,15 +15,17 @@ type selectList struct {
func newSelectList() *selectList {
v := selectList{List: tview.NewList()}
v.SetBorder(true)
v.SetTitle(" Please select a Container ")
v.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
return evt
})
{
v.SetBorder(true)
v.SetTitle(" [aqua::b]Container Selector ")
v.SetInputCapture(func(evt *tcell.EventKey) *tcell.EventKey {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
return evt
})
}
return &v
}

View File

@ -5,7 +5,7 @@ import (
"strings"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
"github.com/derailed/tview"
)
const (

View File

@ -7,149 +7,125 @@ import (
"sync"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/tview"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
)
const (
titleFmt = " [aqua::b]%s[aqua::-]([fuchsia::b]%d[aqua::-]) "
searchFmt = "<[green::b]/%s[aqua::]> "
nsTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])[aqua::-][[aqua::b]%d[aqua::-]][aqua::-] "
)
type (
tableView struct {
*tview.Flex
*tview.Table
app *appView
baseTitle string
currentNS string
refresh sync.Mutex
refreshMX sync.Mutex
actions keyActions
colorer colorerFn
colorerFn colorerFn
sortFn resource.SortFn
table *tview.Table
data resource.TableData
cmdBuff *cmdBuff
}
)
func newTableView(app *appView, title string, sortFn resource.SortFn) *tableView {
v := tableView{app: app, Flex: tview.NewFlex().SetDirection(tview.FlexRow)}
v := tableView{app: app, Table: tview.NewTable()}
{
v.baseTitle = title
v.sortFn = sortFn
v.actions = make(keyActions)
v.SetBorder(true)
v.SetBorderColor(tcell.ColorDodgerBlue)
v.SetBorderAttributes(tcell.AttrBold)
v.SetBorderPadding(0, 0, 1, 1)
v.cmdBuff = newCmdBuff('/')
v.cmdBuff.addListener(app.cmdView)
v.cmdBuff.reset()
v.SetSelectable(true, false)
v.SetSelectedStyle(tcell.ColorBlack, tcell.ColorAqua, tcell.AttrBold)
v.SetInputCapture(v.keyboard)
}
v.cmdBuff.addListener(app.cmdView)
v.cmdBuff.reset()
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd)
v.actions[tcell.KeyEnter] = newKeyAction("Search", v.filterCmd)
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd)
v.actions[tcell.KeyBackspace2] = newKeyAction("Erase", v.eraseCmd)
v.table = tview.NewTable()
{
v.table.SetSelectable(true, false)
v.table.SetSelectedStyle(tcell.ColorBlack, tcell.ColorAqua, tcell.AttrBold)
v.table.SetInputCapture(v.keyboard)
}
v.AddItem(v.table, 0, 1, true)
return &v
}
func (v *tableView) setDeleted() {
r, _ := v.table.GetSelection()
cols := v.table.GetColumnCount()
for x := 0; x < cols; x++ {
v.table.GetCell(r, x).SetAttributes(tcell.AttrDim)
}
}
func (v *tableView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
key := evt.Key()
if evt.Key() == tcell.KeyRune {
if key == tcell.KeyRune {
if v.cmdBuff.isActive() {
v.cmdBuff.add(evt.Rune())
}
switch evt.Rune() {
case v.cmdBuff.hotKey:
if !v.app.cmdView.inCmdMode() {
v.app.flash(flashInfo, "Entering filtering mode...")
log.Info("K9s entering filtering mode...")
v.cmdBuff.setActive(true)
}
return evt
return nil
}
key = tcell.Key(evt.Rune())
}
if a, ok := v.actions[key]; ok {
if !v.app.cmdView.inCmdMode() {
a.action(evt)
}
}
switch evt.Key() {
case tcell.KeyEnter:
if v.cmdBuff.isActive() && !v.cmdBuff.empty() {
v.filter()
}
v.cmdBuff.setActive(false)
case tcell.KeyEsc:
v.cmdBuff.reset()
v.filter()
case tcell.KeyBackspace2:
if v.cmdBuff.isActive() {
v.cmdBuff.del()
}
log.Debug(">> TableView handled ", tcell.KeyNames[key])
return a.action(evt)
}
return evt
}
func (v *tableView) filter() {
v.filterData(v.cmdBuff)
func (v *tableView) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cmdBuff.setActive(false)
v.refresh()
return nil
}
func (v *tableView) filterData(filter fmt.Stringer) {
filtered := resource.TableData{
Header: v.data.Header,
Rows: resource.RowEvents{},
Namespace: v.data.Namespace,
func (v *tableView) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.cmdBuff.isActive() {
v.cmdBuff.del()
}
return nil
}
func (v *tableView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
v.cmdBuff.reset()
v.refresh()
return nil
}
func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
if v.app.cmdView.inCmdMode() {
return evt
}
rx, err := regexp.Compile(filter.String())
if err != nil {
v.app.flash(flashErr, "Invalid search expression")
v.cmdBuff.clear()
return
v.app.flash(flashInfo, "Entering filtering mode...")
log.Info("Entering filtering mode...")
v.cmdBuff.reset()
v.cmdBuff.setActive(true)
return nil
}
func (v *tableView) setDeleted() {
r, _ := v.GetSelection()
cols := v.GetColumnCount()
for x := 0; x < cols; x++ {
v.GetCell(r, x).SetAttributes(tcell.AttrDim)
}
for k, row := range v.data.Rows {
f := strings.Join(row.Fields, " ")
if rx.MatchString(f) {
filtered.Rows[k] = row
}
}
v.doUpdate(filtered)
}
// SetColorer sets up table row color management.
func (v *tableView) SetColorer(f colorerFn) {
v.colorer = f
}
// AddActions sets up keyboard action listener.
func (v *tableView) addActions(kk keyActions) {
for k, a := range kk {
v.actions[k] = a
}
v.colorerFn = f
}
// SetActions sets up keyboard action listener.
func (v *tableView) setActions(aa keyActions) {
v.actions = aa
for k, a := range aa {
v.actions[k] = a
}
}
// Hints options
@ -160,43 +136,53 @@ func (v *tableView) hints() hints {
return nil
}
func (v *tableView) resetTitle() {
var title string
switch v.currentNS {
case resource.NotNamespaced:
title = fmt.Sprintf(titleFmt, v.baseTitle, v.table.GetRowCount()-1)
default:
ns := v.currentNS
if v.currentNS == resource.AllNamespaces {
ns = resource.AllNamespace
}
title = fmt.Sprintf(nsTitleFmt, v.baseTitle, ns, v.table.GetRowCount()-1)
}
if !v.cmdBuff.empty() {
title += fmt.Sprintf("<[green::b]/%s[aqua::]> ", v.cmdBuff)
}
v.SetTitle(title)
func (v *tableView) refresh() {
v.update(v.data)
}
// Update table content
func (v *tableView) update(data resource.TableData) {
v.refresh.Lock()
v.refreshMX.Lock()
{
v.data = data
if !v.cmdBuff.empty() {
v.filter()
if !v.cmdBuff.isActive() && !v.cmdBuff.empty() {
v.doUpdate(v.filtered())
} else {
v.doUpdate(data)
}
v.resetTitle()
}
v.refresh.Unlock()
v.refreshMX.Unlock()
v.resetTitle()
}
func (v *tableView) filtered() resource.TableData {
if v.cmdBuff.empty() {
return v.data
}
rx, err := regexp.Compile(`(?i)` + v.cmdBuff.String())
if err != nil {
v.app.flash(flashErr, "Invalid filter expression")
v.cmdBuff.clear()
return v.data
}
filtered := resource.TableData{
Header: v.data.Header,
Rows: resource.RowEvents{},
Namespace: v.data.Namespace,
}
for k, row := range v.data.Rows {
f := strings.Join(row.Fields, " ")
if rx.MatchString(f) {
filtered.Rows[k] = row
}
}
return filtered
}
func (v *tableView) doUpdate(data resource.TableData) {
v.table.Clear()
v.Clear()
v.currentNS = data.Namespace
var row int
@ -209,7 +195,7 @@ func (v *tableView) doUpdate(data resource.TableData) {
}
c.SetTextColor(tcell.ColorWhite)
}
v.table.SetCell(row, col, c)
v.SetCell(row, col, c)
}
row++
@ -217,11 +203,13 @@ func (v *tableView) doUpdate(data resource.TableData) {
for k := range data.Rows {
keys = append(keys, k)
}
v.sortFn(keys)
if v.sortFn != nil {
v.sortFn(keys)
}
for _, k := range keys {
fgColor := tcell.ColorGray
if v.colorer != nil {
fgColor = v.colorer(data.Namespace, data.Rows[k])
if v.colorerFn != nil {
fgColor = v.colorerFn(data.Namespace, data.Rows[k])
}
for col, f := range data.Rows[k].Fields {
c := tview.NewTableCell(deltas(data.Rows[k].Deltas[col], f))
@ -232,12 +220,36 @@ func (v *tableView) doUpdate(data resource.TableData) {
}
c.SetTextColor(fgColor)
}
v.table.SetCell(row, col, c)
v.SetCell(row, col, c)
}
row++
}
}
func (v *tableView) resetTitle() {
var title string
rc := v.GetRowCount()
if rc > 0 {
rc--
}
switch v.currentNS {
case resource.NotNamespaced:
title = fmt.Sprintf(titleFmt, v.baseTitle, rc)
default:
ns := v.currentNS
if v.currentNS == resource.AllNamespaces {
ns = resource.AllNamespace
}
title = fmt.Sprintf(nsTitleFmt, v.baseTitle, ns, rc)
}
if !v.cmdBuff.isActive() && !v.cmdBuff.empty() {
title += fmt.Sprintf(searchFmt, v.cmdBuff)
}
v.SetTitle(title)
}
// ----------------------------------------------------------------------------
// Event listeners...

View File

@ -1,91 +0,0 @@
package views
import (
"context"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
)
type xrayView struct {
*tview.Table
app *appView
actions keyActions
}
func newXrayView(app *appView) *xrayView {
v := xrayView{app: app, Table: tview.NewTable()}
v.SetBorder(true)
v.SetTitle(" Details ")
v.SetTitleColor(tcell.ColorAqua)
v.SetSelectable(true, false)
v.SetSelectedStyle(tcell.ColorBlack, tcell.ColorAqua, tcell.AttrNone)
v.SetInputCapture(v.keyboard)
return &v
}
func (v *xrayView) setTitle(t string) {
v.Table.SetTitle(t)
}
func (v *xrayView) clear() {
v.Table.Clear()
}
func (v *xrayView) blur() {
v.Table.Blur()
}
func (v *xrayView) init(_ context.Context) {
}
// SetActions to handle keyboard inputs
func (v *xrayView) setActions(aa keyActions) {
v.actions = aa
}
// Hints fetch mmemonic and hints
func (v *xrayView) hints() hints {
if v.actions != nil {
return v.actions.toHints()
}
return nil
}
func (v *xrayView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if evt.Key() == tcell.KeyRune {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
} else {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
}
return evt
}
func (v *xrayView) update(pp resource.Properties) {
v.Clear()
var row int
for col, h := range pp["Headers"].(resource.Row) {
tc := tview.NewTableCell(h)
tc.SetExpansion(2)
v.SetCell(0, col, tc)
}
row++
for _, r := range pp["Rows"].([]resource.Row) {
for col, c := range r {
tc := tview.NewTableCell(c)
tc.SetExpansion(2)
v.SetCell(row, col, tc)
}
row++
}
}

View File

@ -1,157 +0,0 @@
package views
import (
"context"
"fmt"
"sort"
"strings"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
)
const (
keyColor = "#00FF00"
valColor = "#ADFF2F"
yamlTitleFmt = " [aqua::-] YAML [orange::-]%s"
)
type yamlView struct {
*tview.TextView
app *appView
actions keyActions
}
func newYamlView(app *appView) *yamlView {
v := yamlView{app: app, TextView: tview.NewTextView()}
{
v.SetBorder(true)
v.SetDynamicColors(true)
v.SetWrap(false)
v.SetTitleColor(tcell.ColorAqua)
v.SetInputCapture(v.keyboard)
}
return &v
}
func (v *yamlView) setTitle(t string) {
v.TextView.SetTitle(fmt.Sprintf(yamlTitleFmt, t))
}
func (v *yamlView) clear() {
v.TextView.Clear()
}
func (v *yamlView) blur() {
}
func (v *yamlView) init(_ context.Context) {
}
// SetActions to handle keyboard inputs
func (v *yamlView) setActions(aa keyActions) {
v.actions = aa
}
func (v *yamlView) hints() hints {
if v.actions != nil {
return v.actions.toHints()
}
return nil
}
func (v *yamlView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
if evt.Key() == tcell.KeyRune {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
} else {
if a, ok := v.actions[evt.Key()]; ok {
a.action(evt)
evt = nil
}
}
return evt
}
func (v *yamlView) isEmpty(c interface{}) bool {
switch c.(type) {
case []string:
return len(c.([]string)) == 0
case map[string]string:
return len(c.(map[string]string)) == 0
case map[string]interface{}:
return len(c.(map[string]interface{})) == 0
}
return false
}
func (v *yamlView) update(pp resource.Properties) {
v.Clear()
kk := make([]string, 0, len(pp))
for k := range pp {
kk = append(kk, k)
}
sort.Strings(kk)
var level int
for _, k := range kk {
if v.isEmpty(pp[k]) {
continue
}
fmt.Fprintf(v, "[%s::b]%-20s", keyColor, k+":")
v.textFor(level, pp[k])
fmt.Fprintf(v, "\n")
}
v.ScrollToBeginning()
v.app.Draw()
}
func (v *yamlView) textFor(level int, p interface{}) string {
var indent string
for i := 0; i < level; i++ {
if level < 1 {
indent += strings.Repeat(" ", 20)
} else {
indent += strings.Repeat(" ", 2)
}
}
switch p.(type) {
case string:
fmt.Fprintf(v, "[%s::-]%s", valColor, p.(string))
case []string:
aa := p.([]string)
for i, s := range aa {
if i == 0 {
fmt.Fprintf(v, "[%s::-]%s", valColor, s)
} else {
if level < 1 {
fmt.Fprintf(v, "%s[%s::-]%-20s%s", indent, valColor, "", s)
} else {
fmt.Fprintf(v, "%s[%s::-]%-13s%s", indent, valColor, "", s)
}
}
if i+1 < len(aa) {
fmt.Fprintf(v, "\n")
}
}
case map[string]interface{}:
m := p.(map[string]interface{})
indent += strings.Repeat(" ", 2)
fmt.Fprintf(v, "\n")
var i int
for key, val := range m {
fmt.Fprintf(v, "%s[%s::b]%-12s ", indent, keyColor, key+":")
v.textFor(level+1, val)
i++
if i < len(m) {
fmt.Fprintf(v, "\n")
}
}
}
return indent
}