dataraces + skins
parent
3e36512e19
commit
445b475c21
|
|
@ -7,7 +7,6 @@ k9s
|
||||||
/k8s
|
/k8s
|
||||||
dist
|
dist
|
||||||
notes
|
notes
|
||||||
styles
|
|
||||||
vendor
|
vendor
|
||||||
go.mod1
|
go.mod1
|
||||||
popeye1.go
|
popeye1.go
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,8 @@ snapcraft:
|
||||||
By leveraging a terminal UI, you can easily traverse Kubernetes resources
|
By leveraging a terminal UI, you can easily traverse Kubernetes resources
|
||||||
and view the state of you clusters in a single powerful session.
|
and view the state of you clusters in a single powerful session.
|
||||||
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
||||||
publish: false
|
# publish: false
|
||||||
|
publish: true
|
||||||
replacements:
|
replacements:
|
||||||
amd64: 64-bit
|
amd64: 64-bit
|
||||||
386: 32-bit
|
386: 32-bit
|
||||||
|
|
@ -76,16 +77,15 @@ snapcraft:
|
||||||
bit: Arm
|
bit: Arm
|
||||||
bitv6: Arm6
|
bitv6: Arm6
|
||||||
bitv7: Arm7
|
bitv7: Arm7
|
||||||
grade: devel
|
# grade: devel
|
||||||
confinement: devmode
|
# confinement: devmode
|
||||||
|
grade: stable
|
||||||
|
confinement: strict
|
||||||
apps:
|
apps:
|
||||||
k9s:
|
k9s:
|
||||||
plugs: ["home", "network"]
|
# plugs: ["home", "network"]
|
||||||
# plugs: ["home", "network", "personal-files"]
|
plugs: ["home", "network", "personal-files"]
|
||||||
# plugs:
|
plugs:
|
||||||
# personal-files:
|
personal-files:
|
||||||
# read:
|
read:
|
||||||
# - $HOME/.k9s
|
- $HOME/.kube
|
||||||
# - $HOME/.kube
|
|
||||||
# write:
|
|
||||||
# - $HOME/.k9s
|
|
||||||
221
README.md
221
README.md
|
|
@ -249,6 +249,227 @@ roleRef:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Skins
|
||||||
|
|
||||||
|
You can style K9s based on your own sense of style and look. This is very much an experimental feature at this time, more will be added/modified if this feature has legs so thread accordingly!
|
||||||
|
|
||||||
|
Skins are YAML files, that enable a user to change K9s presentation layer. K9s skins are loaded from `$HOME/.k9s/skin.yml`. If a skin file is detected then the skin would be loaded if not the current stock skin remains in effect.
|
||||||
|
|
||||||
|
Below is a sample skin file, more skins would be available in the skins directory, just simply copy any of these in your user's home dir as `skin.yml`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# InTheNavy Skin...
|
||||||
|
k9s:
|
||||||
|
# General K9s styles
|
||||||
|
fgColor: dodgerblue
|
||||||
|
bgColor: white
|
||||||
|
logoColor: blue
|
||||||
|
# ClusterInfoView styles.
|
||||||
|
info:
|
||||||
|
fgColor: lightskyblue
|
||||||
|
sectionColor: steelblue
|
||||||
|
# Borders styles.
|
||||||
|
border:
|
||||||
|
fgColor: dodgerblue
|
||||||
|
focusColor: aliceblue
|
||||||
|
# MenuView attributes and styles.
|
||||||
|
menu:
|
||||||
|
fgColor: darkblue
|
||||||
|
keyColor: cornflowerblue
|
||||||
|
# Used for favorite namespaces
|
||||||
|
numKeyColor: cadetblue
|
||||||
|
# CrumbView attributes for history navigation.
|
||||||
|
crumb:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: steelblue
|
||||||
|
# Active view settings
|
||||||
|
activeColor: skyblue
|
||||||
|
# TableView attributes.
|
||||||
|
table:
|
||||||
|
fgColor: blue
|
||||||
|
bgColor: darkblue
|
||||||
|
cursorColor: aqua
|
||||||
|
# Header row styles.
|
||||||
|
header:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: darkblue
|
||||||
|
sorterColor: orange
|
||||||
|
# Resource status and update styles
|
||||||
|
status:
|
||||||
|
newColor: blue
|
||||||
|
modifyColor: powderblue
|
||||||
|
addColor: lightskyblue
|
||||||
|
errorColor: indianred
|
||||||
|
highlightcolor: royalblue
|
||||||
|
killColor: slategray
|
||||||
|
completedColor: gray
|
||||||
|
# Border title styles.
|
||||||
|
title:
|
||||||
|
fgColor: aqua
|
||||||
|
bgColor: white
|
||||||
|
highlightColor: skyblue
|
||||||
|
counterColor: slateblue
|
||||||
|
filterColor: slategray
|
||||||
|
# YAML info styles.
|
||||||
|
yaml:
|
||||||
|
keyColor: steelblue
|
||||||
|
colonColor: blue
|
||||||
|
valueColor: royalblue
|
||||||
|
```
|
||||||
|
|
||||||
|
Available color names are defined below:
|
||||||
|
|
||||||
|
| Color Names |
|
||||||
|
|----------------------|
|
||||||
|
| black |
|
||||||
|
| maroon |
|
||||||
|
| green |
|
||||||
|
| olive |
|
||||||
|
| navy |
|
||||||
|
| purple |
|
||||||
|
| teal |
|
||||||
|
| silver |
|
||||||
|
| gray |
|
||||||
|
| red |
|
||||||
|
| lime |
|
||||||
|
| yellow |
|
||||||
|
| blue |
|
||||||
|
| fuchsia |
|
||||||
|
| aqua |
|
||||||
|
| white |
|
||||||
|
| aliceblue |
|
||||||
|
| antiquewhite |
|
||||||
|
| aquamarine |
|
||||||
|
| azure |
|
||||||
|
| beige |
|
||||||
|
| bisque |
|
||||||
|
| blanchedalmond |
|
||||||
|
| blueviolet |
|
||||||
|
| brown |
|
||||||
|
| burlywood |
|
||||||
|
| cadetblue |
|
||||||
|
| chartreuse |
|
||||||
|
| chocolate |
|
||||||
|
| coral |
|
||||||
|
| cornflowerblue |
|
||||||
|
| cornsilk |
|
||||||
|
| crimson |
|
||||||
|
| darkblue |
|
||||||
|
| darkcyan |
|
||||||
|
| darkgoldenrod |
|
||||||
|
| darkgray |
|
||||||
|
| darkgreen |
|
||||||
|
| darkkhaki |
|
||||||
|
| darkmagenta |
|
||||||
|
| darkolivegreen |
|
||||||
|
| darkorange |
|
||||||
|
| darkorchid |
|
||||||
|
| darkred |
|
||||||
|
| darksalmon |
|
||||||
|
| darkseagreen |
|
||||||
|
| darkslateblue |
|
||||||
|
| darkslategray |
|
||||||
|
| darkturquoise |
|
||||||
|
| darkviolet |
|
||||||
|
| deeppink |
|
||||||
|
| deepskyblue |
|
||||||
|
| dimgray |
|
||||||
|
| dodgerblue |
|
||||||
|
| firebrick |
|
||||||
|
| floralwhite |
|
||||||
|
| forestgreen |
|
||||||
|
| gainsboro |
|
||||||
|
| ghostwhite |
|
||||||
|
| gold |
|
||||||
|
| goldenrod |
|
||||||
|
| greenyellow |
|
||||||
|
| honeydew |
|
||||||
|
| hotpink |
|
||||||
|
| indianred |
|
||||||
|
| indigo |
|
||||||
|
| ivory |
|
||||||
|
| khaki |
|
||||||
|
| lavender |
|
||||||
|
| lavenderblush |
|
||||||
|
| lawngreen |
|
||||||
|
| lemonchiffon |
|
||||||
|
| lightblue |
|
||||||
|
| lightcoral |
|
||||||
|
| lightcyan |
|
||||||
|
| lightgoldenrodyellow |
|
||||||
|
| lightgray |
|
||||||
|
| lightgreen |
|
||||||
|
| lightpink |
|
||||||
|
| lightsalmon |
|
||||||
|
| lightseagreen |
|
||||||
|
| lightskyblue |
|
||||||
|
| lightslategray |
|
||||||
|
| lightsteelblue |
|
||||||
|
| lightyellow |
|
||||||
|
| limegreen |
|
||||||
|
| linen |
|
||||||
|
| mediumaquamarine |
|
||||||
|
| mediumblue |
|
||||||
|
| mediumorchid |
|
||||||
|
| mediumpurple |
|
||||||
|
| mediumseagreen |
|
||||||
|
| mediumslateblue |
|
||||||
|
| mediumspringgreen |
|
||||||
|
| mediumturquoise |
|
||||||
|
| mediumvioletred |
|
||||||
|
| midnightblue |
|
||||||
|
| mintcream |
|
||||||
|
| mistyrose |
|
||||||
|
| moccasin |
|
||||||
|
| navajowhite |
|
||||||
|
| oldlace |
|
||||||
|
| olivedrab |
|
||||||
|
| orange |
|
||||||
|
| orangered |
|
||||||
|
| orchid |
|
||||||
|
| palegoldenrod |
|
||||||
|
| palegreen |
|
||||||
|
| paleturquoise |
|
||||||
|
| palevioletred |
|
||||||
|
| papayawhip |
|
||||||
|
| peachpuff |
|
||||||
|
| peru |
|
||||||
|
| pink |
|
||||||
|
| plum |
|
||||||
|
| powderblue |
|
||||||
|
| rebeccapurple |
|
||||||
|
| rosybrown |
|
||||||
|
| royalblue |
|
||||||
|
| saddlebrown |
|
||||||
|
| salmon |
|
||||||
|
| sandybrown |
|
||||||
|
| seagreen |
|
||||||
|
| seashell |
|
||||||
|
| sienna |
|
||||||
|
| skyblue |
|
||||||
|
| slateblue |
|
||||||
|
| slategray |
|
||||||
|
| snow |
|
||||||
|
| springgreen |
|
||||||
|
| steelblue |
|
||||||
|
| tan |
|
||||||
|
| thistle |
|
||||||
|
| tomato |
|
||||||
|
| turquoise |
|
||||||
|
| violet |
|
||||||
|
| wheat |
|
||||||
|
| whitesmoke |
|
||||||
|
| yellowgreen |
|
||||||
|
| grey |
|
||||||
|
| dimgrey |
|
||||||
|
| darkgrey |
|
||||||
|
| darkslategrey |
|
||||||
|
| lightgrey |
|
||||||
|
| lightslategrey |
|
||||||
|
| slategrey |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Disclaimer
|
## Disclaimer
|
||||||
|
|
||||||
This is still work in progress! If there is enough interest in the Kubernetes
|
This is still work in progress! If there is enough interest in the Kubernetes
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.6.0
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Thank you to all that contributed with flushing out issues with K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes!
|
||||||
|
|
||||||
|
If you've filed an issue please help me verify and close.
|
||||||
|
|
||||||
|
Thank you so much for your support and awesome suggestions to make K9s better!!
|
||||||
|
|
||||||
|
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Change Logs
|
||||||
|
|
||||||
|
### Skins
|
||||||
|
|
||||||
|
You can now skin K9s based on your own sense of style. Skinning, is currently limited to color variations and is still very much experimental. More details on how to achieve this is provided in the README and skins directory in this repo. This could be a prime opportunity for someone to contribute to this project and help us come up with some cooler LNF and share with all K9s folks. Any schemes contributed will be added and featured on this repo.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resolved Bugs
|
||||||
|
|
||||||
|
+ [Issue #169](https://github.com/derailed/k9s/issues/169)
|
||||||
|
+ [Issue #171](https://github.com/derailed/k9s/issues/171)
|
||||||
|
+ [Issue #172](https://github.com/derailed/k9s/issues/172)
|
||||||
|
+ [Issue #175](https://github.com/derailed/k9s/issues/175)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2019 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
5
go.mod
5
go.mod
|
|
@ -12,10 +12,12 @@ replace (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-autorest/autorest v0.1.0 // indirect
|
github.com/Azure/go-autorest/autorest v0.1.0 // indirect
|
||||||
github.com/derailed/tview v0.1.4
|
github.com/derailed/tview v0.1.6
|
||||||
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
||||||
github.com/fatih/camelcase v1.0.0 // indirect
|
github.com/fatih/camelcase v1.0.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/gdamore/tcell v1.1.1
|
github.com/gdamore/tcell v1.1.1
|
||||||
|
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae // indirect
|
||||||
github.com/gogo/protobuf v1.2.1 // indirect
|
github.com/gogo/protobuf v1.2.1 // indirect
|
||||||
github.com/google/btree v1.0.0 // indirect
|
github.com/google/btree v1.0.0 // indirect
|
||||||
github.com/google/gofuzz v1.0.0 // indirect
|
github.com/google/gofuzz v1.0.0 // indirect
|
||||||
|
|
@ -30,6 +32,7 @@ require (
|
||||||
github.com/onsi/gomega v1.5.0 // indirect
|
github.com/onsi/gomega v1.5.0 // indirect
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||||
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81
|
github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81
|
||||||
|
github.com/pkg/profile v1.3.0
|
||||||
github.com/rs/zerolog v1.14.3
|
github.com/rs/zerolog v1.14.3
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
github.com/spf13/pflag v1.0.3 // indirect
|
github.com/spf13/pflag v1.0.3 // indirect
|
||||||
|
|
|
||||||
10
go.sum
10
go.sum
|
|
@ -47,6 +47,10 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/derailed/tview v0.1.4 h1:6ZtMtb5+2bbGNH7SldHGcVB8GnSTXKIQwKxWRNb6DxY=
|
github.com/derailed/tview v0.1.4 h1:6ZtMtb5+2bbGNH7SldHGcVB8GnSTXKIQwKxWRNb6DxY=
|
||||||
github.com/derailed/tview v0.1.4/go.mod h1:oLBQyhVeXqeUYWDZk7/5NJVbbq/JFXm3W7oEoEtpmSc=
|
github.com/derailed/tview v0.1.4/go.mod h1:oLBQyhVeXqeUYWDZk7/5NJVbbq/JFXm3W7oEoEtpmSc=
|
||||||
|
github.com/derailed/tview v0.1.5 h1:Gj6K73V9d+zsex208KX8YsAw68+nWVNr7oM9qfTWbXQ=
|
||||||
|
github.com/derailed/tview v0.1.5/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||||
|
github.com/derailed/tview v0.1.6 h1:mmp6Yg78IgbdHapV9wFoVYrbtZbMXilCdAIHadm2uqc=
|
||||||
|
github.com/derailed/tview v0.1.6/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
|
|
@ -71,6 +75,8 @@ github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2H
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||||
|
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae h1:PeVNzgTRtWGm6fVic5i21t+n5ptPGCZuMcSPVMyTWjs=
|
||||||
|
github.com/go-fsnotify/fsnotify v0.0.0-20180321022601-755488143dae/go.mod h1:BbhqyaehKPCLD83cqfRYdm177Ylm1cdGHu3txjbQSQI=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||||
|
|
@ -193,6 +199,8 @@ github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/profile v1.3.0 h1:OQIvuDgm00gWVWGTf4m4mCt6W1/0YqU7Ntg0mySWgaI=
|
||||||
|
github.com/pkg/profile v1.3.0/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
|
@ -211,6 +219,8 @@ github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M=
|
||||||
github.com/rivo/tview v0.0.0-20190213202703-b373355e9db4/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw=
|
github.com/rivo/tview v0.0.0-20190213202703-b373355e9db4/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw=
|
||||||
|
github.com/rivo/uniseg v0.0.0-20190313204849-f699dde9c340 h1:nOZbL5f2xmBAHWYrrHbHV1xatzZirN++oOQ3g83Ypgs=
|
||||||
|
github.com/rivo/uniseg v0.0.0-20190313204849-f699dde9c340/go.mod h1:SOLvOL4ybwgLJ6TYoX/rtaJ8EGOulH4XU7E9/TLrTCE=
|
||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
github.com/rs/zerolog v1.14.3 h1:4EGfSkR2hJDB0s3oFfrlPqjU1e4WLncergLil3nEKW0=
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,9 @@ func (n *Namespace) Validate(c Connection, ks KubeSettings) {
|
||||||
func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
|
func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
|
||||||
log.Debug().Msgf("Setting active ns %s", ns)
|
log.Debug().Msgf("Setting active ns %s", ns)
|
||||||
n.Active = ns
|
n.Active = ns
|
||||||
n.addFavNS(ns)
|
if ns != "" {
|
||||||
|
n.addFavNS(ns)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,302 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/derailed/tview"
|
||||||
|
"github.com/gdamore/tcell"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// K9sStylesFile represents K9s config file location.
|
||||||
|
K9sStylesFile = filepath.Join(K9sHome, "skin.yml")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Styles tracks K9s styling options.
|
||||||
|
Styles struct {
|
||||||
|
Style *Style `yaml:"k9s"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Style tracks K9s styles.
|
||||||
|
Style struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
BgColor string `yaml:"bgColor"`
|
||||||
|
LogoColor string `yaml:"logoColor"`
|
||||||
|
|
||||||
|
Info *Info `yaml:"info"`
|
||||||
|
Border *Border `yaml:"border"`
|
||||||
|
Menu *Menu `yaml:"menu"`
|
||||||
|
Crumb *Crumb `yaml:"crumb"`
|
||||||
|
Table *Table `yaml:"table"`
|
||||||
|
Status *Status `yaml:"status"`
|
||||||
|
Title *Title `yaml:"title"`
|
||||||
|
Yaml *Yaml `yaml:"yaml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status tracks resource status styles.
|
||||||
|
Status struct {
|
||||||
|
NewColor string `yaml:"newColor"`
|
||||||
|
ModifyColor string `yaml:"modifyColor"`
|
||||||
|
AddColor string `yaml:"addColor"`
|
||||||
|
ErrorColor string `yaml:"errorColor"`
|
||||||
|
HighlightColor string `yaml:"highlightColor"`
|
||||||
|
KillColor string `yaml:"killColor"`
|
||||||
|
CompletedColor string `yaml:"completedColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yaml tracks yaml styles.
|
||||||
|
Yaml struct {
|
||||||
|
KeyColor string `yaml:"keyColor"`
|
||||||
|
ValueColor string `yaml:"valueColor"`
|
||||||
|
ColonColor string `yaml:"colonColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title tracks title styles.
|
||||||
|
Title struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
BgColor string `yaml:"bgColor"`
|
||||||
|
HighlightColor string `yaml:"highlightColor"`
|
||||||
|
CounterColor string `yaml:"counterColor"`
|
||||||
|
FilterColor string `yaml:"filterColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info tracks info styles.
|
||||||
|
Info struct {
|
||||||
|
SectionColor string `yaml:"sectionColor"`
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Border tracks border styles.
|
||||||
|
Border struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
FocusColor string `yaml:"focusColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crumb tracks crumbs styles.
|
||||||
|
Crumb struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
BgColor string `yaml:"bgColor"`
|
||||||
|
ActiveColor string `yaml:"activeColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table tracks table styles.
|
||||||
|
Table struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
BgColor string `yaml:"bgColor"`
|
||||||
|
CursorColor string `yaml:"cursorColor"`
|
||||||
|
Header *TableHeader `yaml:"header"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableHeader tracks table header styles.
|
||||||
|
TableHeader struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
BgColor string `yaml:"bgColor"`
|
||||||
|
SorterColor string `yaml:"sorterColor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Menu tracks menu styles.
|
||||||
|
Menu struct {
|
||||||
|
FgColor string `yaml:"fgColor"`
|
||||||
|
KeyColor string `yaml:"keyColor"`
|
||||||
|
NumKeyColor string `yaml:"numKeyColor"`
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func newStyle() *Style {
|
||||||
|
return &Style{
|
||||||
|
FgColor: "cadetblue",
|
||||||
|
BgColor: "black",
|
||||||
|
LogoColor: "orange",
|
||||||
|
Info: newInfo(),
|
||||||
|
Border: newBorder(),
|
||||||
|
Menu: newMenu(),
|
||||||
|
Crumb: newCrumb(),
|
||||||
|
Table: newTable(),
|
||||||
|
Status: newStatus(),
|
||||||
|
Title: newTitle(),
|
||||||
|
Yaml: newYaml(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStatus() *Status {
|
||||||
|
return &Status{
|
||||||
|
NewColor: "lightskyblue",
|
||||||
|
ModifyColor: "greenyellow",
|
||||||
|
AddColor: "white",
|
||||||
|
ErrorColor: "orangered",
|
||||||
|
HighlightColor: "aqua",
|
||||||
|
KillColor: "mediumpurple",
|
||||||
|
CompletedColor: "gray",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewYaml returns a new yaml style.
|
||||||
|
func newYaml() *Yaml {
|
||||||
|
return &Yaml{
|
||||||
|
KeyColor: "steelblue",
|
||||||
|
ColonColor: "white",
|
||||||
|
ValueColor: "papayawhip",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTitle returns a new title style.
|
||||||
|
func newTitle() *Title {
|
||||||
|
return &Title{
|
||||||
|
FgColor: "aqua",
|
||||||
|
BgColor: "black",
|
||||||
|
HighlightColor: "fuchsia",
|
||||||
|
CounterColor: "papayawhip",
|
||||||
|
FilterColor: "steelblue",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInfo returns a new info style.
|
||||||
|
func newInfo() *Info {
|
||||||
|
return &Info{
|
||||||
|
SectionColor: "white",
|
||||||
|
FgColor: "orange",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTable returns a new table style.
|
||||||
|
func newTable() *Table {
|
||||||
|
return &Table{
|
||||||
|
FgColor: "aqua",
|
||||||
|
BgColor: "black",
|
||||||
|
CursorColor: "aqua",
|
||||||
|
Header: newTableHeader(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTableHeader returns a new table header style.
|
||||||
|
func newTableHeader() *TableHeader {
|
||||||
|
return &TableHeader{
|
||||||
|
FgColor: "white",
|
||||||
|
BgColor: "black",
|
||||||
|
SorterColor: "orange",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCrumb returns a new crumbs style.
|
||||||
|
func newCrumb() *Crumb {
|
||||||
|
return &Crumb{
|
||||||
|
FgColor: "black",
|
||||||
|
BgColor: "aqua",
|
||||||
|
ActiveColor: "orange",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBorder returns a new border style.
|
||||||
|
func newBorder() *Border {
|
||||||
|
return &Border{
|
||||||
|
FgColor: "dodgerblue",
|
||||||
|
FocusColor: "lightskyblue",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMenu returns a new menu style.
|
||||||
|
func newMenu() *Menu {
|
||||||
|
return &Menu{
|
||||||
|
FgColor: "white",
|
||||||
|
KeyColor: "dodgerblue",
|
||||||
|
NumKeyColor: "fuchsia",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStyles creates a new default config.
|
||||||
|
func NewStyles() (*Styles, error) {
|
||||||
|
s := &Styles{Style: newStyle()}
|
||||||
|
err := s.load(K9sStylesFile)
|
||||||
|
|
||||||
|
return s, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure default styles are applied in not in stylesheet.
|
||||||
|
func (s *Styles) ensure() {
|
||||||
|
if s.Style == nil {
|
||||||
|
s.Style = newStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Info == nil {
|
||||||
|
s.Style.Info = newInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Border == nil {
|
||||||
|
s.Style.Border = newBorder()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Table == nil {
|
||||||
|
s.Style.Table = newTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Menu == nil {
|
||||||
|
s.Style.Menu = newMenu()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Crumb == nil {
|
||||||
|
s.Style.Crumb = newCrumb()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Status == nil {
|
||||||
|
s.Style.Status = newStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Title == nil {
|
||||||
|
s.Style.Title = newTitle()
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Style.Yaml == nil {
|
||||||
|
s.Style.Yaml = newYaml()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FgColor returns the foreground color.
|
||||||
|
func (s *Styles) FgColor() tcell.Color {
|
||||||
|
return AsColor(s.Style.FgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BgColor returns the background color.
|
||||||
|
func (s *Styles) BgColor() tcell.Color {
|
||||||
|
return AsColor(s.Style.BgColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load K9s configuration from file
|
||||||
|
func (s *Styles) load(path string) error {
|
||||||
|
f, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg Styles
|
||||||
|
if err := yaml.Unmarshal(f, &cfg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Style != nil {
|
||||||
|
s.Style = cfg.Style
|
||||||
|
}
|
||||||
|
s.ensure()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update apply terminal colors based on styles.
|
||||||
|
func (s *Styles) Update() {
|
||||||
|
tview.Styles.PrimitiveBackgroundColor = AsColor(s.Style.BgColor)
|
||||||
|
tview.Styles.ContrastBackgroundColor = AsColor(s.Style.BgColor)
|
||||||
|
tview.Styles.PrimaryTextColor = AsColor(s.Style.FgColor)
|
||||||
|
tview.Styles.BorderColor = AsColor(s.Style.Border.FgColor)
|
||||||
|
tview.Styles.FocusColor = AsColor(s.Style.Border.FocusColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsColor checks color index, if match return color otherwise pink it is.
|
||||||
|
func AsColor(c string) tcell.Color {
|
||||||
|
if color, ok := tcell.ColorNames[c]; ok {
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
return tcell.ColorPink
|
||||||
|
}
|
||||||
|
|
@ -3,8 +3,8 @@ package resource
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
|
|
@ -22,6 +22,7 @@ type (
|
||||||
instance v1.Container
|
instance v1.Container
|
||||||
MetricsServer MetricsServer
|
MetricsServer MetricsServer
|
||||||
metrics k8s.PodMetrics
|
metrics k8s.PodMetrics
|
||||||
|
mx sync.RWMutex
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -82,30 +83,49 @@ func (r *Container) Logs(c chan<- string, ns, n, co string, lines int64, prev bo
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-time.After(defaultTimeout):
|
case <-time.After(defaultTimeout):
|
||||||
if blocked {
|
var closes bool
|
||||||
|
r.mx.RLock()
|
||||||
|
{
|
||||||
|
closes = blocked
|
||||||
|
}
|
||||||
|
r.mx.RUnlock()
|
||||||
|
if closes {
|
||||||
|
log.Debug().Msg(">>Closing Channel<<")
|
||||||
close(c)
|
close(c)
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// This call will block if nothing is in the stream!!
|
// This call will block if nothing is in the stream!!
|
||||||
stream, err := req.Stream()
|
stream, err := req.Stream()
|
||||||
blocked = false
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Msgf("Tail logs failed `%s/%s:%s -- %v", ns, n, co, err)
|
log.Error().Msgf("Tail logs failed `%s/%s:%s -- %v", ns, n, co, err)
|
||||||
return cancel, fmt.Errorf("%v", err)
|
return cancel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.mx.Lock()
|
||||||
|
{
|
||||||
|
blocked = false
|
||||||
|
}
|
||||||
|
r.mx.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
log.Debug().Msg("!!!Closing Stream!!!")
|
||||||
|
close(c)
|
||||||
stream.Close()
|
stream.Close()
|
||||||
cancel()
|
cancel()
|
||||||
close(c)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(stream)
|
scanner := bufio.NewScanner(stream)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
c <- scanner.Text()
|
c <- scanner.Text()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
|
|
@ -40,6 +41,7 @@ type (
|
||||||
instance *v1.Pod
|
instance *v1.Pod
|
||||||
MetricsServer MetricsServer
|
MetricsServer MetricsServer
|
||||||
metrics k8s.PodMetrics
|
metrics k8s.PodMetrics
|
||||||
|
mx sync.RWMutex
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,7 +57,11 @@ func NewPodList(c Connection, mx MetricsServer, ns string) List {
|
||||||
|
|
||||||
// NewPod instantiates a new Pod.
|
// NewPod instantiates a new Pod.
|
||||||
func NewPod(c Connection, mx MetricsServer) *Pod {
|
func NewPod(c Connection, mx MetricsServer) *Pod {
|
||||||
p := &Pod{&Base{Connection: c, Resource: k8s.NewPod(c)}, nil, mx, k8s.PodMetrics{}}
|
p := &Pod{
|
||||||
|
Base: &Base{Connection: c, Resource: k8s.NewPod(c)},
|
||||||
|
MetricsServer: mx,
|
||||||
|
metrics: k8s.PodMetrics{},
|
||||||
|
}
|
||||||
p.Factory = p
|
p.Factory = p
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
@ -123,30 +129,49 @@ func (r *Pod) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (c
|
||||||
go func() {
|
go func() {
|
||||||
select {
|
select {
|
||||||
case <-time.After(defaultTimeout):
|
case <-time.After(defaultTimeout):
|
||||||
if blocked {
|
var closes bool
|
||||||
|
r.mx.RLock()
|
||||||
|
{
|
||||||
|
closes = blocked
|
||||||
|
}
|
||||||
|
r.mx.RUnlock()
|
||||||
|
if closes {
|
||||||
|
log.Debug().Msg(">>Closing Channel<<")
|
||||||
close(c)
|
close(c)
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// This call will block if nothing is in the stream!!
|
// This call will block if nothing is in the stream!!
|
||||||
stream, err := req.Stream()
|
stream, err := req.Stream()
|
||||||
blocked = false
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Msgf("Tail logs failed `%s/%s:%s -- %v", ns, n, co, err)
|
log.Error().Msgf("Tail logs failed `%s/%s:%s -- %v", ns, n, co, err)
|
||||||
return cancel, fmt.Errorf("%v", err)
|
return cancel, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r.mx.Lock()
|
||||||
|
{
|
||||||
|
blocked = false
|
||||||
|
}
|
||||||
|
r.mx.Unlock()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
|
log.Debug().Msg("!!!Closing Stream!!!")
|
||||||
|
close(c)
|
||||||
stream.Close()
|
stream.Close()
|
||||||
cancel()
|
cancel()
|
||||||
close(c)
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
scanner := bufio.NewScanner(stream)
|
scanner := bufio.NewScanner(stream)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
c <- scanner.Text()
|
c <- scanner.Text()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,18 +3,22 @@ package views
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||||
)
|
)
|
||||||
|
|
||||||
const splashTime = 1
|
const (
|
||||||
|
splashTime = 1
|
||||||
|
devMode = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
focusHandler func(tview.Primitive)
|
focusHandler func(tview.Primitive)
|
||||||
|
|
@ -45,6 +49,7 @@ type (
|
||||||
*tview.Application
|
*tview.Application
|
||||||
|
|
||||||
config *config.Config
|
config *config.Config
|
||||||
|
styles *config.Styles
|
||||||
version string
|
version string
|
||||||
flags *genericclioptions.ConfigFlags
|
flags *genericclioptions.ConfigFlags
|
||||||
pages *tview.Pages
|
pages *tview.Pages
|
||||||
|
|
@ -61,7 +66,6 @@ type (
|
||||||
cmdBuff *cmdBuff
|
cmdBuff *cmdBuff
|
||||||
cmdView *cmdView
|
cmdView *cmdView
|
||||||
actions keyActions
|
actions keyActions
|
||||||
mx sync.Mutex
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -69,15 +73,16 @@ type (
|
||||||
func NewApp(cfg *config.Config) *appView {
|
func NewApp(cfg *config.Config) *appView {
|
||||||
v := appView{Application: tview.NewApplication(), config: cfg}
|
v := appView{Application: tview.NewApplication(), config: cfg}
|
||||||
{
|
{
|
||||||
|
v.refreshStyles()
|
||||||
v.pages = tview.NewPages()
|
v.pages = tview.NewPages()
|
||||||
v.actions = make(keyActions)
|
v.actions = make(keyActions)
|
||||||
v.menuView = newMenuView()
|
v.menuView = newMenuView(&v)
|
||||||
v.content = tview.NewPages()
|
v.content = tview.NewPages()
|
||||||
v.cmdBuff = newCmdBuff(':')
|
v.cmdBuff = newCmdBuff(':')
|
||||||
v.cmdView = newCmdView('🐶')
|
v.cmdView = newCmdView(&v, '🐶')
|
||||||
v.command = newCommand(&v)
|
v.command = newCommand(&v)
|
||||||
v.flashView = newFlashView(v.Application, "Initializing...")
|
v.flashView = newFlashView(&v, "Initializing...")
|
||||||
v.crumbsView = newCrumbsView(v.Application)
|
v.crumbsView = newCrumbsView(&v)
|
||||||
v.clusterInfoView = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
v.clusterInfoView = newClusterInfoView(&v, k8s.NewMetricsServer(cfg.GetConnection()))
|
||||||
v.focusChanged = v.changedFocus
|
v.focusChanged = v.changedFocus
|
||||||
v.SetInputCapture(v.keyboard)
|
v.SetInputCapture(v.keyboard)
|
||||||
|
|
@ -108,21 +113,21 @@ func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags)
|
||||||
header.SetDirection(tview.FlexColumn)
|
header.SetDirection(tview.FlexColumn)
|
||||||
header.AddItem(a.clusterInfoView, 35, 1, false)
|
header.AddItem(a.clusterInfoView, 35, 1, false)
|
||||||
header.AddItem(a.menuView, 0, 1, false)
|
header.AddItem(a.menuView, 0, 1, false)
|
||||||
header.AddItem(logoView(), 26, 1, false)
|
header.AddItem(a.logoView(), 26, 1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
main := tview.NewFlex()
|
main := tview.NewFlex()
|
||||||
{
|
{
|
||||||
main.SetDirection(tview.FlexRow)
|
main.SetDirection(tview.FlexRow)
|
||||||
main.AddItem(header, 7, 1, false)
|
main.AddItem(header, 7, 1, false)
|
||||||
main.AddItem(a.cmdView, 1, 1, false)
|
main.AddItem(a.cmdView, 3, 1, false)
|
||||||
main.AddItem(a.content, 0, 10, true)
|
main.AddItem(a.content, 0, 10, true)
|
||||||
main.AddItem(a.crumbsView, 2, 1, false)
|
main.AddItem(a.crumbsView, 2, 1, false)
|
||||||
main.AddItem(a.flashView, 1, 1, false)
|
main.AddItem(a.flashView, 1, 1, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.pages.AddPage("main", main, true, false)
|
a.pages.AddPage("main", main, true, false)
|
||||||
a.pages.AddPage("splash", newSplash(a.version), true, true)
|
a.pages.AddPage("splash", newSplash(a), true, true)
|
||||||
a.SetRoot(a.pages, true)
|
a.SetRoot(a.pages, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,12 +135,52 @@ func (a *appView) conn() k8s.Connection {
|
||||||
return a.config.GetConnection()
|
return a.config.GetConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *appView) stylesUpdater() (*fsnotify.Watcher, error) {
|
||||||
|
w, err := fsnotify.NewWatcher()
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("File notifier failed")
|
||||||
|
return w, err
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case evt := <-w.Events:
|
||||||
|
log.Debug().Msgf("Evt %#v", evt)
|
||||||
|
a.QueueUpdateDraw(func() {
|
||||||
|
a.refreshStyles()
|
||||||
|
})
|
||||||
|
case err := <-w.Errors:
|
||||||
|
log.Error().Err(err).Msg("Watcher failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err := w.Add(config.K9sStylesFile); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Styles file watch failed")
|
||||||
|
return w, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Run starts the application loop
|
// Run starts the application loop
|
||||||
func (a *appView) Run() {
|
func (a *appView) Run() {
|
||||||
|
// Only enable updater while in dev mode.
|
||||||
|
if a.version == devMode {
|
||||||
|
w, err := a.stylesUpdater()
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
w.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-time.After(splashTime * time.Second)
|
<-time.After(splashTime * time.Second)
|
||||||
a.showPage("main")
|
a.QueueUpdateDraw(func() {
|
||||||
a.Draw()
|
a.showPage("main")
|
||||||
|
})
|
||||||
}()
|
}()
|
||||||
|
|
||||||
a.command.defaultCmd()
|
a.command.defaultCmd()
|
||||||
|
|
@ -145,9 +190,6 @@ func (a *appView) Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
a.mx.Lock()
|
|
||||||
defer a.mx.Unlock()
|
|
||||||
|
|
||||||
key := evt.Key()
|
key := evt.Key()
|
||||||
if key == tcell.KeyRune {
|
if key == tcell.KeyRune {
|
||||||
if a.cmdBuff.isActive() {
|
if a.cmdBuff.isActive() {
|
||||||
|
|
@ -156,10 +198,12 @@ func (a *appView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
}
|
}
|
||||||
key = tcell.Key(evt.Rune())
|
key = tcell.Key(evt.Rune())
|
||||||
}
|
}
|
||||||
|
|
||||||
if a, ok := a.actions[key]; ok {
|
if a, ok := a.actions[key]; ok {
|
||||||
log.Debug().Msgf(">> AppView handled key: %s", tcell.KeyNames[key])
|
log.Debug().Msgf(">> AppView handled key: %s -- %d", tcell.KeyNames[key], runtime.NumGoroutine())
|
||||||
return a.action(evt)
|
return a.action(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,8 +260,8 @@ func (a *appView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if a.cmdView.inCmdMode() {
|
if a.cmdView.inCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
a.flash(flashInfo, "Entering command mode...")
|
a.flash(flashInfo, "Command mode activated.")
|
||||||
log.Debug().Msg("Entering app command mode...")
|
log.Debug().Msg("Entering command mode...")
|
||||||
a.cmdBuff.setActive(true)
|
a.cmdBuff.setActive(true)
|
||||||
a.cmdBuff.clear()
|
a.cmdBuff.clear()
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -290,10 +334,6 @@ func (a *appView) cmdMode() bool {
|
||||||
return a.cmdView.inCmdMode()
|
return a.cmdView.inCmdMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *appView) refresh() {
|
|
||||||
a.clusterInfoView.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *appView) flash(level flashLevel, m ...string) {
|
func (a *appView) flash(level flashLevel, m ...string) {
|
||||||
a.flashView.setMessage(level, m...)
|
a.flashView.setMessage(level, m...)
|
||||||
}
|
}
|
||||||
|
|
@ -302,14 +342,14 @@ func (a *appView) setHints(h hints) {
|
||||||
a.menuView.populateMenu(h)
|
a.menuView.populateMenu(h)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logoView() tview.Primitive {
|
func (a *appView) logoView() tview.Primitive {
|
||||||
v := tview.NewTextView()
|
v := tview.NewTextView()
|
||||||
{
|
{
|
||||||
v.SetWordWrap(false)
|
v.SetWordWrap(false)
|
||||||
v.SetWrap(false)
|
v.SetWrap(false)
|
||||||
v.SetDynamicColors(true)
|
v.SetDynamicColors(true)
|
||||||
for i, s := range LogoSmall {
|
for i, s := range LogoSmall {
|
||||||
fmt.Fprintf(v, "[orange::b]%s", s)
|
fmt.Fprintf(v, "[%s::b]%s", a.styles.Style.LogoColor, s)
|
||||||
if i+1 < len(LogoSmall) {
|
if i+1 < len(LogoSmall) {
|
||||||
fmt.Fprintf(v, "\n")
|
fmt.Fprintf(v, "\n")
|
||||||
}
|
}
|
||||||
|
|
@ -348,9 +388,17 @@ func (a *appView) nextFocus() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func initStyles() {
|
func (a *appView) refreshStyles() {
|
||||||
tview.Styles.PrimitiveBackgroundColor = tcell.ColorBlack
|
var err error
|
||||||
tview.Styles.ContrastBackgroundColor = tcell.ColorBlack
|
if a.styles, err = config.NewStyles(); err != nil {
|
||||||
tview.Styles.FocusColor = tcell.ColorLightSkyBlue
|
log.Error().Err(err).Msg("No skin file found. Loading defaults.")
|
||||||
tview.Styles.BorderColor = tcell.ColorDodgerBlue
|
}
|
||||||
|
a.styles.Update()
|
||||||
|
|
||||||
|
stdColor = config.AsColor(a.styles.Style.Status.NewColor)
|
||||||
|
addColor = config.AsColor(a.styles.Style.Status.AddColor)
|
||||||
|
modColor = config.AsColor(a.styles.Style.Status.ModifyColor)
|
||||||
|
errColor = config.AsColor(a.styles.Style.Status.ErrorColor)
|
||||||
|
highlightColor = config.AsColor(a.styles.Style.Status.HighlightColor)
|
||||||
|
completedColor = config.AsColor(a.styles.Style.Status.CompletedColor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package views
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
|
@ -62,20 +63,25 @@ func (v *clusterInfoView) init() {
|
||||||
v.SetCell(row, 1, v.infoCell("n/a"))
|
v.SetCell(row, 1, v.infoCell("n/a"))
|
||||||
v.SetCell(row+1, 0, v.sectionCell("MEM"))
|
v.SetCell(row+1, 0, v.sectionCell("MEM"))
|
||||||
v.SetCell(row+1, 1, v.infoCell("n/a"))
|
v.SetCell(row+1, 1, v.infoCell("n/a"))
|
||||||
|
|
||||||
v.refresh()
|
v.refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*clusterInfoView) sectionCell(t string) *tview.TableCell {
|
func (v *clusterInfoView) sectionCell(t string) *tview.TableCell {
|
||||||
c := tview.NewTableCell(t + ":")
|
c := tview.NewTableCell(t + ":")
|
||||||
c.SetAlign(tview.AlignLeft)
|
c.SetAlign(tview.AlignLeft)
|
||||||
|
var s tcell.Style
|
||||||
|
c.SetStyle(s.Bold(true).Foreground(config.AsColor(v.app.styles.Style.Info.SectionColor)))
|
||||||
|
c.SetBackgroundColor(v.app.styles.BgColor())
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*clusterInfoView) infoCell(t string) *tview.TableCell {
|
func (v *clusterInfoView) infoCell(t string) *tview.TableCell {
|
||||||
c := tview.NewTableCell(t)
|
c := tview.NewTableCell(t)
|
||||||
c.SetExpansion(2)
|
c.SetExpansion(2)
|
||||||
c.SetTextColor(tcell.ColorOrange)
|
c.SetTextColor(config.AsColor(v.app.styles.Style.Info.FgColor))
|
||||||
|
c.SetBackgroundColor(v.app.styles.BgColor())
|
||||||
|
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@ package views
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPrompt = "%c> %s"
|
const defaultPrompt = "%c> %s"
|
||||||
|
|
@ -16,16 +15,20 @@ type cmdView struct {
|
||||||
activated bool
|
activated bool
|
||||||
icon rune
|
icon rune
|
||||||
text string
|
text string
|
||||||
|
app *appView
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCmdView(ic rune) *cmdView {
|
func newCmdView(app *appView, ic rune) *cmdView {
|
||||||
v := cmdView{icon: ic, TextView: tview.NewTextView()}
|
v := cmdView{app: app, icon: ic, TextView: tview.NewTextView()}
|
||||||
{
|
{
|
||||||
v.SetWordWrap(true)
|
v.SetWordWrap(true)
|
||||||
v.SetWrap(true)
|
v.SetWrap(true)
|
||||||
v.SetDynamicColors(true)
|
v.SetDynamicColors(true)
|
||||||
|
v.SetBorder(true)
|
||||||
v.SetBorderPadding(0, 0, 1, 1)
|
v.SetBorderPadding(0, 0, 1, 1)
|
||||||
v.SetTextColor(tcell.ColorAqua)
|
v.SetBackgroundColor(app.styles.BgColor())
|
||||||
|
v.SetBorderColor(config.AsColor(app.styles.Style.Border.FocusColor))
|
||||||
|
v.SetTextColor(app.styles.FgColor())
|
||||||
}
|
}
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
@ -62,11 +65,12 @@ func (v *cmdView) changed(s string) {
|
||||||
func (v *cmdView) active(f bool) {
|
func (v *cmdView) active(f bool) {
|
||||||
v.activated = f
|
v.activated = f
|
||||||
if f {
|
if f {
|
||||||
log.Debug().Msg("CmdView was activated...")
|
v.SetBorder(true)
|
||||||
v.SetBackgroundColor(tcell.ColorDodgerBlue)
|
v.SetTextColor(v.app.styles.FgColor())
|
||||||
v.activate()
|
v.activate()
|
||||||
} else {
|
} else {
|
||||||
v.SetBackgroundColor(tcell.ColorDefault)
|
v.SetBorder(false)
|
||||||
|
v.SetBackgroundColor(v.app.styles.BgColor())
|
||||||
v.Clear()
|
v.Clear()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
package views
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
const maxBuff = 10
|
const maxBuff = 10
|
||||||
|
|
||||||
type buffWatcher interface {
|
type buffWatcher interface {
|
||||||
|
|
@ -37,6 +41,7 @@ func (c *cmdBuff) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *cmdBuff) add(r rune) {
|
func (c *cmdBuff) add(r rune) {
|
||||||
|
log.Debug().Msgf("Add %s", string(r))
|
||||||
c.buff = append(c.buff, r)
|
c.buff = append(c.buff, r)
|
||||||
c.fireChanged()
|
c.fireChanged()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,14 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var (
|
||||||
modColor = tcell.ColorGreenYellow
|
modColor tcell.Color
|
||||||
addColor = tcell.ColorLightSkyBlue
|
addColor tcell.Color
|
||||||
errColor = tcell.ColorOrangeRed
|
errColor tcell.Color
|
||||||
stdColor = tcell.ColorWhite
|
stdColor tcell.Color
|
||||||
highlightColor = tcell.ColorAqua
|
highlightColor tcell.Color
|
||||||
killColor = tcell.ColorMediumPurple
|
killColor tcell.Color
|
||||||
completedColor = tcell.ColorDodgerBlue
|
completedColor tcell.Color
|
||||||
)
|
)
|
||||||
|
|
||||||
func defaultColorer(ns string, r *resource.RowEvent) tcell.Color {
|
func defaultColorer(ns string, r *resource.RowEvent) tcell.Color {
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ func newContainerView(t string, app *appView, list resource.List, path string) r
|
||||||
}
|
}
|
||||||
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
|
v.AddPage("logs", newLogsView(list.GetName(), &v), true, false)
|
||||||
v.switchPage("co")
|
v.switchPage("co")
|
||||||
|
v.selChanged(1, 0)
|
||||||
|
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,33 +4,36 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type crumbsView struct {
|
type crumbsView struct {
|
||||||
*tview.TextView
|
*tview.TextView
|
||||||
|
|
||||||
app *tview.Application
|
app *appView
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCrumbsView(app *tview.Application) *crumbsView {
|
func newCrumbsView(app *appView) *crumbsView {
|
||||||
v := crumbsView{app: app, TextView: tview.NewTextView()}
|
v := crumbsView{app: app, TextView: tview.NewTextView()}
|
||||||
{
|
{
|
||||||
v.SetTextColor(tcell.ColorAqua)
|
v.SetBackgroundColor(app.styles.BgColor())
|
||||||
v.SetTextAlign(tview.AlignLeft)
|
v.SetTextAlign(tview.AlignLeft)
|
||||||
v.SetBorderPadding(0, 0, 1, 1)
|
v.SetBorderPadding(0, 0, 1, 1)
|
||||||
v.SetDynamicColors(true)
|
v.SetDynamicColors(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *crumbsView) update(crumbs []string) {
|
func (v *crumbsView) update(crumbs []string) {
|
||||||
v.Clear()
|
v.Clear()
|
||||||
last, bgColor := len(crumbs)-1, "aqua"
|
last, bgColor := len(crumbs)-1, v.app.styles.Style.Crumb.BgColor
|
||||||
for i, c := range crumbs {
|
for i, c := range crumbs {
|
||||||
if i == last {
|
if i == last {
|
||||||
bgColor = "orange"
|
bgColor = v.app.styles.Style.Crumb.ActiveColor
|
||||||
}
|
}
|
||||||
fmt.Fprintf(v, "[black:%s:b] <%s> [-:-:-] ", bgColor, c)
|
fmt.Fprintf(v, "[%s:%s:b] <%s> [-:%s:-] ",
|
||||||
|
v.app.styles.Style.Crumb.FgColor,
|
||||||
|
bgColor, c,
|
||||||
|
v.app.styles.Style.BgColor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,14 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const detailsTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])[aqua::-] "
|
const detailsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-] "
|
||||||
|
|
||||||
// detailsView displays text output.
|
// detailsView displays text output.
|
||||||
type detailsView struct {
|
type detailsView struct {
|
||||||
|
|
@ -25,7 +25,6 @@ type detailsView struct {
|
||||||
cmdBuff *cmdBuff
|
cmdBuff *cmdBuff
|
||||||
backFn actionHandler
|
backFn actionHandler
|
||||||
numSelections int
|
numSelections int
|
||||||
mx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDetailsView(app *appView, backFn actionHandler) *detailsView {
|
func newDetailsView(app *appView, backFn actionHandler) *detailsView {
|
||||||
|
|
@ -37,6 +36,7 @@ func newDetailsView(app *appView, backFn actionHandler) *detailsView {
|
||||||
v.SetDynamicColors(true)
|
v.SetDynamicColors(true)
|
||||||
v.SetRegions(true)
|
v.SetRegions(true)
|
||||||
v.SetBorder(true)
|
v.SetBorder(true)
|
||||||
|
v.SetBorderFocusColor(config.AsColor(v.app.styles.Style.Border.FocusColor))
|
||||||
v.SetHighlightColor(tcell.ColorOrange)
|
v.SetHighlightColor(tcell.ColorOrange)
|
||||||
v.SetTitleColor(tcell.ColorAqua)
|
v.SetTitleColor(tcell.ColorAqua)
|
||||||
v.SetInputCapture(v.keyboard)
|
v.SetInputCapture(v.keyboard)
|
||||||
|
|
@ -65,9 +65,6 @@ func (v *detailsView) setCategory(n string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *detailsView) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
v.mx.Lock()
|
|
||||||
defer v.mx.Unlock()
|
|
||||||
|
|
||||||
key := evt.Key()
|
key := evt.Key()
|
||||||
if key == tcell.KeyRune {
|
if key == tcell.KeyRune {
|
||||||
if v.cmdBuff.isActive() {
|
if v.cmdBuff.isActive() {
|
||||||
|
|
@ -198,9 +195,16 @@ func (v *detailsView) refreshTitle() {
|
||||||
|
|
||||||
func (v *detailsView) setTitle(t string) {
|
func (v *detailsView) setTitle(t string) {
|
||||||
v.title = t
|
v.title = t
|
||||||
title := fmt.Sprintf(detailsTitleFmt, v.category, t)
|
|
||||||
|
fmat := strings.Replace(detailsTitleFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[hilite", "["+v.app.styles.Style.Title.CounterColor, 1)
|
||||||
|
title := fmt.Sprintf(fmat, v.category, t)
|
||||||
if !v.cmdBuff.empty() {
|
if !v.cmdBuff.empty() {
|
||||||
title += fmt.Sprintf(searchFmt, v.cmdBuff.String())
|
fmat := strings.Replace(searchFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[filter", "["+v.app.styles.Style.Title.FilterColor, 1)
|
||||||
|
title += fmt.Sprintf(fmat, v.cmdBuff.String())
|
||||||
}
|
}
|
||||||
v.SetTitle(title)
|
v.SetTitle(title)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,42 +30,44 @@ type (
|
||||||
*tview.TextView
|
*tview.TextView
|
||||||
|
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
app *tview.Application
|
app *appView
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func newFlashView(app *tview.Application, m string) *flashView {
|
func newFlashView(app *appView, m string) *flashView {
|
||||||
f := flashView{app: app, TextView: tview.NewTextView()}
|
f := flashView{app: app, TextView: tview.NewTextView()}
|
||||||
{
|
f.SetTextColor(tcell.ColorAqua)
|
||||||
f.SetTextColor(tcell.ColorAqua)
|
f.SetTextAlign(tview.AlignLeft)
|
||||||
f.SetTextAlign(tview.AlignLeft)
|
f.SetBorderPadding(0, 0, 1, 1)
|
||||||
f.SetBorderPadding(0, 0, 1, 1)
|
f.SetText("")
|
||||||
f.SetText("")
|
|
||||||
}
|
|
||||||
return &f
|
return &f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *flashView) setMessage(level flashLevel, msg ...string) {
|
func (v *flashView) setMessage(level flashLevel, msg ...string) {
|
||||||
if f.cancel != nil {
|
if v.cancel != nil {
|
||||||
f.cancel()
|
v.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _, width, _ := v.GetRect()
|
||||||
|
if width <= 15 {
|
||||||
|
width = 100
|
||||||
|
}
|
||||||
|
m := strings.Join(msg, " ")
|
||||||
|
v.SetTextColor(flashColor(level))
|
||||||
|
v.SetText(resource.Truncate(flashEmoji(level)+" "+m, width-3))
|
||||||
|
|
||||||
var ctx context.Context
|
var ctx context.Context
|
||||||
{
|
{
|
||||||
ctx, f.cancel = context.WithTimeout(context.TODO(), flashDelay*time.Second)
|
ctx, v.cancel = context.WithTimeout(context.TODO(), flashDelay*time.Second)
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
_, _, width, _ := f.GetRect()
|
|
||||||
if width <= 15 {
|
|
||||||
width = 100
|
|
||||||
}
|
|
||||||
m := strings.Join(msg, " ")
|
|
||||||
f.SetTextColor(flashColor(level))
|
|
||||||
f.SetText(resource.Truncate(flashEmoji(level)+" "+m, width-3))
|
|
||||||
f.app.Draw()
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
f.Clear()
|
v.app.QueueUpdateDraw(func() {
|
||||||
f.app.Draw()
|
v.Clear()
|
||||||
|
// v.app.Draw()
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package views
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
)
|
)
|
||||||
|
|
@ -28,14 +29,23 @@ func newLogView(title string, parent loggable) *logView {
|
||||||
return &v
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logView) logLine(line string, scroll bool) {
|
func (l *logView) logLine(line string) {
|
||||||
fmt.Fprintln(l.ansiWriter, tview.Escape(line))
|
fmt.Fprintln(l.ansiWriter, tview.Escape(line))
|
||||||
if scroll {
|
|
||||||
l.ScrollToEnd()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *logView) log(lines fmt.Stringer) {
|
func (l *logView) log(lines fmt.Stringer) {
|
||||||
l.Clear()
|
l.Clear()
|
||||||
fmt.Fprintln(l.ansiWriter, lines.String())
|
fmt.Fprintln(l.ansiWriter, lines.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *logView) flush(index int, buff []string, scroll bool) {
|
||||||
|
if index > 0 {
|
||||||
|
l.logLine(strings.Join(buff[:index], "\n"))
|
||||||
|
if scroll {
|
||||||
|
l.app.QueueUpdate(func() {
|
||||||
|
l.ScrollToEnd()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
index = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package views
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
|
@ -17,6 +17,7 @@ const (
|
||||||
maxBuff1 int64 = 200
|
maxBuff1 int64 = 200
|
||||||
refreshRate = 200 * time.Millisecond
|
refreshRate = 200 * time.Millisecond
|
||||||
maxCleanse = 100
|
maxCleanse = 100
|
||||||
|
logBuffSize = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
type logsView struct {
|
type logsView struct {
|
||||||
|
|
@ -29,7 +30,6 @@ type logsView struct {
|
||||||
cancelFunc context.CancelFunc
|
cancelFunc context.CancelFunc
|
||||||
autoScroll bool
|
autoScroll bool
|
||||||
showPrevious bool
|
showPrevious bool
|
||||||
mx sync.Mutex
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newLogsView(pview string, parent loggable) *logsView {
|
func newLogsView(pview string, parent loggable) *logsView {
|
||||||
|
|
@ -130,8 +130,9 @@ func (v *logsView) stop() {
|
||||||
if v.cancelFunc == nil {
|
if v.cancelFunc == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Debug().Msg("Canceling logs...")
|
|
||||||
v.cancelFunc()
|
v.cancelFunc()
|
||||||
|
log.Debug().Msgf("Canceling logs... %d", runtime.NumGoroutine())
|
||||||
v.cancelFunc = nil
|
v.cancelFunc = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,47 +140,57 @@ func (v *logsView) load(i int) {
|
||||||
if i < 0 || i > len(v.containers)-1 {
|
if i < 0 || i > len(v.containers)-1 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v.SwitchToPage(v.containers[i])
|
v.SwitchToPage(v.containers[i])
|
||||||
if err := v.doLoad(v.parent.getSelection(), v.containers[i]); err != nil {
|
if err := v.doLoad(v.parent.getSelection(), v.containers[i]); err != nil {
|
||||||
v.parent.appView().flash(flashErr, err.Error())
|
v.parent.appView().flash(flashErr, err.Error())
|
||||||
l := v.CurrentPage().Item.(*logView)
|
l := v.CurrentPage().Item.(*logView)
|
||||||
l.logLine("😂 Doh! No logs are available at this time. Check again later on...", false)
|
l.logLine("😂 Doh! No logs are available at this time. Check again later on...")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
v.parent.appView().SetFocus(v)
|
v.parent.appView().SetFocus(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *logsView) doLoad(path, co string) error {
|
func (v *logsView) doLoad(path, co string) error {
|
||||||
v.mx.Lock()
|
|
||||||
defer v.mx.Unlock()
|
|
||||||
|
|
||||||
v.stop()
|
v.stop()
|
||||||
|
|
||||||
c := make(chan string)
|
maxBuff := int64(v.parent.appView().config.K9s.LogRequestSize)
|
||||||
go func() {
|
l := v.CurrentPage().Item.(*logView)
|
||||||
l := v.CurrentPage().Item.(*logView)
|
l.Clear()
|
||||||
l.Clear()
|
l.setTitle(path + ":" + co)
|
||||||
l.setTitle(path + ":" + co)
|
|
||||||
|
c := make(chan string, 10)
|
||||||
|
go func(l *logView) {
|
||||||
|
buff, index := make([]string, logBuffSize), 0
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case line, ok := <-c:
|
case line, ok := <-c:
|
||||||
if !ok {
|
if !ok {
|
||||||
if v.autoScroll {
|
l.flush(index, buff, v.autoScroll)
|
||||||
l.ScrollToEnd()
|
index = 0
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
l.logLine(line, v.autoScroll)
|
if index < logBuffSize {
|
||||||
|
buff[index] = line
|
||||||
|
index++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
l.flush(index, buff, v.autoScroll)
|
||||||
|
index = 0
|
||||||
|
buff[index] = line
|
||||||
|
case <-time.After(1 * time.Second):
|
||||||
|
l.flush(index, buff, v.autoScroll)
|
||||||
|
index = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}(l)
|
||||||
|
|
||||||
ns, po := namespaced(path)
|
ns, po := namespaced(path)
|
||||||
res, ok := v.parent.getList().Resource().(resource.Tailable)
|
res, ok := v.parent.getList().Resource().(resource.Tailable)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource)
|
return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource)
|
||||||
}
|
}
|
||||||
maxBuff := int64(v.parent.appView().config.K9s.LogRequestSize)
|
|
||||||
cancelFn, err := res.Logs(c, ns, po, co, maxBuff, v.showPrevious)
|
cancelFn, err := res.Logs(c, ns, po, co, maxBuff, v.showPrevious)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cancelFn()
|
cancelFn()
|
||||||
|
|
@ -196,9 +207,9 @@ func (v *logsView) doLoad(path, co string) error {
|
||||||
func (v *logsView) toggleScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *logsView) toggleScrollCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
v.autoScroll = !v.autoScroll
|
v.autoScroll = !v.autoScroll
|
||||||
if v.autoScroll {
|
if v.autoScroll {
|
||||||
v.parent.appView().flash(flashInfo, "Autoscroll is on")
|
v.parent.appView().flash(flashInfo, "Autoscroll is on.")
|
||||||
} else {
|
} else {
|
||||||
v.parent.appView().flash(flashInfo, "Autoscroll is off")
|
v.parent.appView().flash(flashInfo, "Autoscroll is off.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -208,7 +219,7 @@ func (v *logsView) backCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
v.stop()
|
v.stop()
|
||||||
v.parent.switchPage(v.parentView)
|
v.parent.switchPage(v.parentView)
|
||||||
|
|
||||||
return nil
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *logsView) topCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *logsView) topCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
|
|
@ -16,12 +15,10 @@ import (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
initKeys()
|
initKeys()
|
||||||
initStyles()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
menuSepFmt = " [dodgerblue::b]%-%ds [white::d]%s "
|
menuIndexFmt = " [key:bg:b]<%d> [fg:bg:d]%s "
|
||||||
menuIndexFmt = " [fuchsia::b]<%d> [white::d]%s "
|
|
||||||
maxRows = 7
|
maxRows = 7
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -77,11 +74,6 @@ func newKeyAction(d string, a actionHandler, display bool) keyAction {
|
||||||
return keyAction{description: d, action: a, visible: display}
|
return keyAction{description: d, action: a, visible: display}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMenuView() *menuView {
|
|
||||||
v := menuView{Table: tview.NewTable()}
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a keyActions) toHints() hints {
|
func (a keyActions) toHints() hints {
|
||||||
kk := make([]int, 0, len(a))
|
kk := make([]int, 0, len(a))
|
||||||
for k, v := range a {
|
for k, v := range a {
|
||||||
|
|
@ -108,16 +100,19 @@ func (a keyActions) toHints() hints {
|
||||||
type menuView struct {
|
type menuView struct {
|
||||||
*tview.Table
|
*tview.Table
|
||||||
|
|
||||||
mx sync.Mutex
|
app *appView
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMenuView(app *appView) *menuView {
|
||||||
|
v := menuView{Table: tview.NewTable(), app: app}
|
||||||
|
v.SetBackgroundColor(app.styles.BgColor())
|
||||||
|
|
||||||
|
return &v
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *menuView) populateMenu(hh hints) {
|
func (v *menuView) populateMenu(hh hints) {
|
||||||
v.mx.Lock()
|
|
||||||
defer v.mx.Unlock()
|
|
||||||
|
|
||||||
v.Clear()
|
v.Clear()
|
||||||
sort.Sort(hh)
|
sort.Sort(hh)
|
||||||
|
|
||||||
t := v.buildMenuTable(hh)
|
t := v.buildMenuTable(hh)
|
||||||
for row := 0; row < len(t); row++ {
|
for row := 0; row < len(t); row++ {
|
||||||
for col := 0; col < len(t[row]); col++ {
|
for col := 0; col < len(t[row]); col++ {
|
||||||
|
|
@ -125,6 +120,7 @@ func (v *menuView) populateMenu(hh hints) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c := tview.NewTableCell(t[row][col])
|
c := tview.NewTableCell(t[row][col])
|
||||||
|
c.SetBackgroundColor(v.app.styles.BgColor())
|
||||||
v.SetCell(row, col, c)
|
v.SetCell(row, col, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -143,9 +139,6 @@ func (v *menuView) buildMenuTable(hh hints) [][]string {
|
||||||
maxKeys := make([]int, colCount+1)
|
maxKeys := make([]int, colCount+1)
|
||||||
for _, h := range hh {
|
for _, h := range hh {
|
||||||
isDigit := menuRX.MatchString(h.mnemonic)
|
isDigit := menuRX.MatchString(h.mnemonic)
|
||||||
// if isDigit && firstNS {
|
|
||||||
// row, col, firstNS = 0, 2, false
|
|
||||||
// }
|
|
||||||
if !isDigit && firstCmd {
|
if !isDigit && firstCmd {
|
||||||
row, col, firstCmd = 0, col+1, false
|
row, col, firstCmd = 0, col+1, false
|
||||||
}
|
}
|
||||||
|
|
@ -184,11 +177,17 @@ func (*menuView) toMnemonic(s string) string {
|
||||||
func (v *menuView) formatMenu(h hint, size int) string {
|
func (v *menuView) formatMenu(h hint, size int) string {
|
||||||
i, err := strconv.Atoi(h.mnemonic)
|
i, err := strconv.Atoi(h.mnemonic)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Sprintf(menuIndexFmt, i, resource.Truncate(h.description, 14))
|
fmat := strings.Replace(menuIndexFmt, "[key", "["+v.app.styles.Style.Menu.NumKeyColor, 1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[fg", "["+v.app.styles.Style.Menu.FgColor, 1)
|
||||||
|
return fmt.Sprintf(fmat, i, resource.Truncate(h.description, 14))
|
||||||
}
|
}
|
||||||
|
|
||||||
menuFmt := " [dodgerblue::b]%-" + strconv.Itoa(size+2) + "s [white::d]%s "
|
menuFmt := " [key:bg:b]%-" + strconv.Itoa(size+2) + "s [fg:bg:d]%s "
|
||||||
return fmt.Sprintf(menuFmt, v.toMnemonic(h.mnemonic), h.description)
|
fmat := strings.Replace(menuFmt, "[key", "["+v.app.styles.Style.Menu.KeyColor, 1)
|
||||||
|
fmat = strings.Replace(fmat, "[fg", "["+v.app.styles.Style.Menu.FgColor, 1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
return fmt.Sprintf(fmat, v.toMnemonic(h.mnemonic), h.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,11 @@ func showPods(app *appView, ns, res, selected, labelSel, fieldSel string, b acti
|
||||||
|
|
||||||
title := fmt.Sprintf("%s:%s Pods", res, selected)
|
title := fmt.Sprintf("%s:%s Pods", res, selected)
|
||||||
pv := newPodView(title, app, list)
|
pv := newPodView(title, app, list)
|
||||||
|
pv.setColorerFn(podColorer)
|
||||||
pv.setExtraActionsFn(func(aa keyActions) {
|
pv.setExtraActionsFn(func(aa keyActions) {
|
||||||
aa[tcell.KeyEsc] = newKeyAction("Back", b, true)
|
aa[tcell.KeyEsc] = newKeyAction("Back", b, true)
|
||||||
})
|
})
|
||||||
|
// Reset active namespace to all.
|
||||||
|
app.config.SetActiveNamespace("")
|
||||||
app.inject(pv)
|
app.inject(pv)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
|
@ -10,7 +11,7 @@ import (
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const fmat = "[aqua::b]%s([fuchsia::b]%s[aqua::-])"
|
const containerFmt = "[fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])"
|
||||||
|
|
||||||
type podView struct {
|
type podView struct {
|
||||||
*resourceView
|
*resourceView
|
||||||
|
|
@ -61,7 +62,17 @@ func (v *podView) listContainers(app *appView, _, res, sel string) {
|
||||||
|
|
||||||
mx := k8s.NewMetricsServer(app.conn())
|
mx := k8s.NewMetricsServer(app.conn())
|
||||||
list := resource.NewContainerList(app.conn(), mx, po)
|
list := resource.NewContainerList(app.conn(), mx, po)
|
||||||
app.inject(newContainerView(fmt.Sprintf(fmat, "Containers", sel), app, list, namespacedName(po.Namespace, po.Name)))
|
|
||||||
|
fmat := strings.Replace(containerFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(containerFmt, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[hilite", "["+v.app.styles.Style.Title.CounterColor, 1)
|
||||||
|
|
||||||
|
app.inject(newContainerView(
|
||||||
|
fmt.Sprintf(fmat, "Containers", sel),
|
||||||
|
app,
|
||||||
|
list,
|
||||||
|
namespacedName(po.Namespace, po.Name),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol...
|
// Protocol...
|
||||||
|
|
@ -88,6 +99,7 @@ func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if v.viewLogs(false) {
|
if v.viewLogs(false) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,6 +107,7 @@ func (v *podView) prevLogsCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if v.viewLogs(true) {
|
if v.viewLogs(true) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ const (
|
||||||
|
|
||||||
all = "*"
|
all = "*"
|
||||||
rbacTitle = "RBAC"
|
rbacTitle = "RBAC"
|
||||||
rbacTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])"
|
rbacTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -28,6 +28,7 @@ type (
|
||||||
rbacView struct {
|
rbacView struct {
|
||||||
*tableView
|
*tableView
|
||||||
|
|
||||||
|
app *appView
|
||||||
current igniter
|
current igniter
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
roleType roleKind
|
roleType roleKind
|
||||||
|
|
@ -78,7 +79,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRBACView(app *appView, ns, name string, kind roleKind) *rbacView {
|
func newRBACView(app *appView, ns, name string, kind roleKind) *rbacView {
|
||||||
v := rbacView{}
|
v := rbacView{app: app}
|
||||||
{
|
{
|
||||||
v.roleName, v.roleType = name, kind
|
v.roleName, v.roleType = name, kind
|
||||||
v.tableView = newTableView(app, v.getTitle())
|
v.tableView = newTableView(app, v.getTitle())
|
||||||
|
|
@ -125,7 +126,11 @@ func (v *rbacView) bindKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *rbacView) getTitle() string {
|
func (v *rbacView) getTitle() string {
|
||||||
return fmt.Sprintf(rbacTitleFmt, rbacTitle, v.roleName)
|
fmat := strings.Replace(rbacTitleFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[hilite", "["+v.app.styles.Style.Title.HighlightColor, 1)
|
||||||
|
|
||||||
|
return fmt.Sprintf(fmat, rbacTitle, v.roleName)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *rbacView) hints() hints {
|
func (v *rbacView) hints() hints {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
|
@ -17,10 +16,7 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const noSelection = ""
|
||||||
refreshDelay = 0.1
|
|
||||||
noSelection = ""
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
type (
|
||||||
details interface {
|
details interface {
|
||||||
|
|
@ -40,7 +36,6 @@ type (
|
||||||
selectedRow int
|
selectedRow int
|
||||||
namespaces map[int]string
|
namespaces map[int]string
|
||||||
selectedNS string
|
selectedNS string
|
||||||
update sync.Mutex
|
|
||||||
list resource.List
|
list resource.List
|
||||||
enterFn enterFn
|
enterFn enterFn
|
||||||
extraActionsFn func(keyActions)
|
extraActionsFn func(keyActions)
|
||||||
|
|
@ -90,13 +85,16 @@ func (v *resourceView) init(ctx context.Context, ns string) {
|
||||||
log.Debug().Msgf("%s watcher canceled!", v.title)
|
log.Debug().Msgf("%s watcher canceled!", v.title)
|
||||||
return
|
return
|
||||||
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second):
|
case <-time.After(time.Duration(v.app.config.K9s.RefreshRate) * time.Second):
|
||||||
v.refresh()
|
v.app.QueueUpdate(func() {
|
||||||
|
v.refresh()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(ctx)
|
}(ctx)
|
||||||
v.refresh()
|
v.refresh()
|
||||||
if tv, ok := v.CurrentPage().Item.(*tableView); ok {
|
if tv, ok := v.CurrentPage().Item.(*tableView); ok {
|
||||||
tv.Select(0, 0)
|
tv.Select(1, 0)
|
||||||
|
v.selChanged(1, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,6 +116,7 @@ func (v *resourceView) getSelectedItem() string {
|
||||||
if v.selectedFn != nil {
|
if v.selectedFn != nil {
|
||||||
return v.selectedFn()
|
return v.selectedFn()
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.selectedItem
|
return v.selectedItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,7 +219,7 @@ func (v *resourceView) defaultEnter(app *appView, ns, resource, selection string
|
||||||
details.setCategory("Describe")
|
details.setCategory("Describe")
|
||||||
details.setTitle(sel)
|
details.setTitle(sel)
|
||||||
details.SetTextColor(tcell.ColorAqua)
|
details.SetTextColor(tcell.ColorAqua)
|
||||||
details.SetText(colorizeYAML(yaml))
|
details.SetText(colorizeYAML(v.app.styles.Style, yaml))
|
||||||
details.ScrollToBeginning()
|
details.ScrollToBeginning()
|
||||||
}
|
}
|
||||||
v.switchPage("details")
|
v.switchPage("details")
|
||||||
|
|
@ -239,6 +238,7 @@ func (v *resourceView) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if !v.rowSelected() {
|
if !v.rowSelected() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
sel := v.getSelectedItem()
|
sel := v.getSelectedItem()
|
||||||
raw, err := v.list.Resource().Marshal(sel)
|
raw, err := v.list.Resource().Marshal(sel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -251,10 +251,11 @@ func (v *resourceView) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
details.setCategory("View")
|
details.setCategory("View")
|
||||||
details.setTitle(sel)
|
details.setTitle(sel)
|
||||||
details.SetTextColor(tcell.ColorMediumAquamarine)
|
details.SetTextColor(tcell.ColorMediumAquamarine)
|
||||||
details.SetText(colorizeYAML(raw))
|
details.SetText(colorizeYAML(v.app.styles.Style, raw))
|
||||||
details.ScrollToBeginning()
|
details.ScrollToBeginning()
|
||||||
}
|
}
|
||||||
v.switchPage("details")
|
v.switchPage("details")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,6 +263,7 @@ func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if !v.rowSelected() {
|
if !v.rowSelected() {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
ns, po := namespaced(v.selectedItem)
|
ns, po := namespaced(v.selectedItem)
|
||||||
args := make([]string, 0, 10)
|
args := make([]string, 0, 10)
|
||||||
args = append(args, "edit")
|
args = append(args, "edit")
|
||||||
|
|
@ -270,6 +272,7 @@ func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
args = append(args, "--context", v.app.config.K9s.CurrentContext)
|
args = append(args, "--context", v.app.config.K9s.CurrentContext)
|
||||||
args = append(args, po)
|
args = append(args, po)
|
||||||
runK(true, v.app, args...)
|
runK(true, v.app, args...)
|
||||||
|
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -282,21 +285,17 @@ func (v *resourceView) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *resourceView) doSwitchNamespace(ns string) {
|
func (v *resourceView) doSwitchNamespace(ns string) {
|
||||||
v.update.Lock()
|
if ns == "" {
|
||||||
{
|
ns = resource.AllNamespace
|
||||||
if ns == noSelection {
|
|
||||||
ns = resource.AllNamespace
|
|
||||||
}
|
|
||||||
v.selectedNS = ns
|
|
||||||
v.app.flash(flashInfo, fmt.Sprintf("Viewing `%s namespace...", ns))
|
|
||||||
v.list.SetNamespace(v.selectedNS)
|
|
||||||
}
|
}
|
||||||
v.update.Unlock()
|
v.selectedNS = ns
|
||||||
|
v.app.flash(flashInfo, fmt.Sprintf("Viewing `%s namespace...", ns))
|
||||||
|
v.list.SetNamespace(v.selectedNS)
|
||||||
|
|
||||||
v.refresh()
|
v.refresh()
|
||||||
v.selectItem(0, 0)
|
|
||||||
v.getTV().resetTitle()
|
v.getTV().resetTitle()
|
||||||
v.getTV().Select(0, 0)
|
v.getTV().Select(1, 0)
|
||||||
|
v.selectItem(1, 0)
|
||||||
v.app.cmdBuff.reset()
|
v.app.cmdBuff.reset()
|
||||||
v.app.config.SetActiveNamespace(v.selectedNS)
|
v.app.config.SetActiveNamespace(v.selectedNS)
|
||||||
v.app.config.Save()
|
v.app.config.Save()
|
||||||
|
|
@ -307,32 +306,32 @@ func (v *resourceView) refresh() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
v.update.Lock()
|
if v.list.Namespaced() {
|
||||||
{
|
v.list.SetNamespace(v.selectedNS)
|
||||||
if v.list.Namespaced() {
|
|
||||||
v.list.SetNamespace(v.selectedNS)
|
|
||||||
}
|
|
||||||
if err := v.list.Reconcile(); err != nil {
|
|
||||||
log.Error().Err(err).Msg("Reconciliation failed")
|
|
||||||
v.app.flash(flashErr, err.Error())
|
|
||||||
}
|
|
||||||
data := v.list.Data()
|
|
||||||
if v.decorateFn != nil {
|
|
||||||
data = v.decorateFn(data)
|
|
||||||
}
|
|
||||||
v.getTV().update(data)
|
|
||||||
v.selectItem(v.selectedRow, 0)
|
|
||||||
v.refreshActions()
|
|
||||||
v.app.clusterInfoView.refresh()
|
|
||||||
v.app.Draw()
|
|
||||||
}
|
}
|
||||||
v.update.Unlock()
|
|
||||||
|
v.refreshActions()
|
||||||
|
v.app.clusterInfoView.refresh()
|
||||||
|
|
||||||
|
if err := v.list.Reconcile(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Reconciliation failed")
|
||||||
|
v.app.flash(flashErr, err.Error())
|
||||||
|
}
|
||||||
|
data := v.list.Data()
|
||||||
|
if v.decorateFn != nil {
|
||||||
|
data = v.decorateFn(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.getTV().update(data)
|
||||||
|
v.selectItem(v.selectedRow, 0)
|
||||||
|
v.app.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *resourceView) getTV() *tableView {
|
func (v *resourceView) getTV() *tableView {
|
||||||
if tv, ok := v.GetPrimitive(v.list.GetName()).(*tableView); ok {
|
if tv, ok := v.GetPrimitive(v.list.GetName()).(*tableView); ok {
|
||||||
return tv
|
return tv
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -360,18 +359,11 @@ func (v *resourceView) selectItem(r, c int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *resourceView) switchPage(p string) {
|
func (v *resourceView) switchPage(p string) {
|
||||||
v.update.Lock()
|
v.SwitchToPage(p)
|
||||||
{
|
v.selectedNS = v.list.GetNamespace()
|
||||||
v.SwitchToPage(p)
|
if h, ok := v.GetPrimitive(p).(hinter); ok {
|
||||||
v.selectedNS = v.list.GetNamespace()
|
v.app.setHints(h.hints())
|
||||||
if h, ok := v.GetPrimitive(p).(hinter); ok {
|
|
||||||
v.app.setHints(h.hints())
|
|
||||||
v.app.SetFocus(v.CurrentPage().Item)
|
|
||||||
} else {
|
|
||||||
log.Error().Msgf("Hinter not implemented on %s", p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
v.update.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *resourceView) rowSelected() bool {
|
func (v *resourceView) rowSelected() bool {
|
||||||
|
|
@ -389,7 +381,7 @@ func (v *resourceView) refreshActions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
var nn []interface{}
|
var nn []interface{}
|
||||||
if !v.list.HasSelectors() && k8s.CanIAccess(v.app.conn().Config(), log.Logger, "", "list", "namespaces", "namespace.v1") {
|
if k8s.CanIAccess(v.app.conn().Config(), log.Logger, "", "list", "namespaces", "namespace.v1") {
|
||||||
var err error
|
var err error
|
||||||
nn, err = k8s.NewNamespace(v.app.conn()).List(resource.AllNamespaces)
|
nn, err = k8s.NewNamespace(v.app.conn()).List(resource.AllNamespaces)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -413,7 +405,6 @@ func (v *resourceView) refreshActions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, false)
|
v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, false)
|
||||||
|
|
||||||
v.actions[tcell.KeyCtrlR] = newKeyAction("Refresh", v.refreshCmd, false)
|
v.actions[tcell.KeyCtrlR] = newKeyAction("Refresh", v.refreshCmd, false)
|
||||||
v.actions[KeyHelp] = newKeyAction("Help", v.app.noopCmd, false)
|
v.actions[KeyHelp] = newKeyAction("Help", v.app.noopCmd, false)
|
||||||
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
|
v.actions[KeyP] = newKeyAction("Previous", v.app.prevCmd, false)
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func (v *secretView) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
details.setCategory("Decoder")
|
details.setCategory("Decoder")
|
||||||
details.setTitle(sel)
|
details.setTitle(sel)
|
||||||
details.SetTextColor(tcell.ColorMediumAquamarine)
|
details.SetTextColor(tcell.ColorMediumAquamarine)
|
||||||
details.SetText(colorizeYAML(string(raw)))
|
details.SetText(colorizeYAML(v.app.styles.Style, string(raw)))
|
||||||
details.ScrollToBeginning()
|
details.ScrollToBeginning()
|
||||||
}
|
}
|
||||||
v.switchPage("details")
|
v.switchPage("details")
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,13 @@ var Logo = []string{
|
||||||
// Splash screen definition
|
// Splash screen definition
|
||||||
type splashView struct {
|
type splashView struct {
|
||||||
*tview.Flex
|
*tview.Flex
|
||||||
|
|
||||||
|
app *appView
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSplash instantiates a new splash screen with product and company info.
|
// NewSplash instantiates a new splash screen with product and company info.
|
||||||
func newSplash(rev string) *splashView {
|
func newSplash(app *appView) *splashView {
|
||||||
v := splashView{tview.NewFlex()}
|
v := splashView{Flex: tview.NewFlex(), app: app}
|
||||||
|
|
||||||
logo := tview.NewTextView()
|
logo := tview.NewTextView()
|
||||||
{
|
{
|
||||||
|
|
@ -56,7 +58,7 @@ func newSplash(rev string) *splashView {
|
||||||
vers.SetBackgroundColor(tcell.ColorDefault)
|
vers.SetBackgroundColor(tcell.ColorDefault)
|
||||||
vers.SetTextAlign(tview.AlignCenter)
|
vers.SetTextAlign(tview.AlignCenter)
|
||||||
}
|
}
|
||||||
v.layoutRev(vers, rev)
|
v.layoutRev(vers, app.version)
|
||||||
|
|
||||||
v.SetDirection(tview.FlexRow)
|
v.SetDirection(tview.FlexRow)
|
||||||
v.AddItem(logo, 10, 1, false)
|
v.AddItem(logo, 10, 1, false)
|
||||||
|
|
@ -65,10 +67,13 @@ func newSplash(rev string) *splashView {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *splashView) layoutLogo(t *tview.TextView) {
|
func (v *splashView) layoutLogo(t *tview.TextView) {
|
||||||
logo := strings.Join(Logo, "\n[orange::b]")
|
logo := strings.Join(Logo, fmt.Sprintf("\n[%s::b]", v.app.styles.Style.LogoColor))
|
||||||
fmt.Fprintf(t, "%s[orange::b]%s\n", strings.Repeat("\n", 2), logo)
|
fmt.Fprintf(t, "%s[%s::b]%s\n",
|
||||||
|
strings.Repeat("\n", 2),
|
||||||
|
v.app.styles.Style.LogoColor,
|
||||||
|
logo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *splashView) layoutRev(t *tview.TextView, rev string) {
|
func (v *splashView) layoutRev(t *tview.TextView, rev string) {
|
||||||
fmt.Fprintf(t, "[white::b]Revision [red::b]%s", rev)
|
fmt.Fprintf(t, "[%s::b]Revision [red::b]%s", v.app.styles.Style.FgColor, rev)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
const subjectTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])"
|
|
||||||
|
|
||||||
var subjectHeader = resource.Row{"NAME", "KIND", "FIRST LOCATION"}
|
var subjectHeader = resource.Row{"NAME", "KIND", "FIRST LOCATION"}
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -177,13 +175,11 @@ func (v *subjectView) reconcile() (resource.TableData, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return table, err
|
return table, err
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("Cluster evts %d", len(evts))
|
|
||||||
|
|
||||||
nevts, err := v.namespacedSubjects()
|
nevts, err := v.namespacedSubjects()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return table, err
|
return table, err
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("NS evts %d", len(nevts))
|
|
||||||
for k, v := range nevts {
|
for k, v := range nevts {
|
||||||
evts[k] = v
|
evts[k] = v
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
"github.com/derailed/tview"
|
"github.com/derailed/tview"
|
||||||
"github.com/gdamore/tcell"
|
"github.com/gdamore/tcell"
|
||||||
|
|
@ -17,9 +17,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
titleFmt = " [aqua::b]%s[aqua::-][[fuchsia::b]%d[aqua::-]] "
|
titleFmt = "[fg:bg:b] %s[fg:bg:-][[count:bg:b]%d[fg:bg:-]] "
|
||||||
searchFmt = "<[green::b]/%s[aqua::]> "
|
searchFmt = "<[filter:bg:b]/%s[fg:bg:]> "
|
||||||
nsTitleFmt = " [aqua::b]%s([fuchsia::b]%s[aqua::-])[aqua::-][[aqua::b]%d[aqua::-]][aqua::-] "
|
nsTitleFmt = "[fg:bg:b] %s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] "
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
|
@ -38,7 +38,6 @@ type (
|
||||||
app *appView
|
app *appView
|
||||||
baseTitle string
|
baseTitle string
|
||||||
currentNS string
|
currentNS string
|
||||||
refreshMX sync.Mutex
|
|
||||||
actions keyActions
|
actions keyActions
|
||||||
colorerFn colorerFn
|
colorerFn colorerFn
|
||||||
sortFn sortFn
|
sortFn sortFn
|
||||||
|
|
@ -46,7 +45,6 @@ type (
|
||||||
data resource.TableData
|
data resource.TableData
|
||||||
cmdBuff *cmdBuff
|
cmdBuff *cmdBuff
|
||||||
sortBuff *cmdBuff
|
sortBuff *cmdBuff
|
||||||
tableMX sync.Mutex
|
|
||||||
sortCol sortColumn
|
sortCol sortColumn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -58,15 +56,20 @@ func newTableView(app *appView, title string) *tableView {
|
||||||
v.actions = make(keyActions)
|
v.actions = make(keyActions)
|
||||||
v.SetFixed(1, 0)
|
v.SetFixed(1, 0)
|
||||||
v.SetBorder(true)
|
v.SetBorder(true)
|
||||||
v.SetFixed(1, 0)
|
v.SetBackgroundColor(config.AsColor(app.styles.Style.Table.BgColor))
|
||||||
v.SetBorderColor(tcell.ColorDodgerBlue)
|
v.SetBorderColor(config.AsColor(app.styles.Style.Table.FgColor))
|
||||||
|
v.SetBorderFocusColor(config.AsColor(app.styles.Style.Border.FocusColor))
|
||||||
v.SetBorderAttributes(tcell.AttrBold)
|
v.SetBorderAttributes(tcell.AttrBold)
|
||||||
v.SetBorderPadding(0, 0, 1, 1)
|
v.SetBorderPadding(0, 0, 1, 1)
|
||||||
v.cmdBuff = newCmdBuff('/')
|
v.cmdBuff = newCmdBuff('/')
|
||||||
v.cmdBuff.addListener(app.cmdView)
|
v.cmdBuff.addListener(app.cmdView)
|
||||||
v.cmdBuff.reset()
|
v.cmdBuff.reset()
|
||||||
v.SetSelectable(true, false)
|
v.SetSelectable(true, false)
|
||||||
v.SetSelectedStyle(tcell.ColorBlack, tcell.ColorAqua, tcell.AttrBold)
|
v.SetSelectedStyle(
|
||||||
|
tcell.ColorBlack,
|
||||||
|
config.AsColor(app.styles.Style.Table.CursorColor),
|
||||||
|
tcell.AttrBold,
|
||||||
|
)
|
||||||
v.SetInputCapture(v.keyboard)
|
v.SetInputCapture(v.keyboard)
|
||||||
v.bindKeys()
|
v.bindKeys()
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +208,7 @@ func (v *tableView) activateCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
v.app.flash(flashInfo, "Filtering...")
|
v.app.flash(flashInfo, "Filter mode activated.")
|
||||||
v.cmdBuff.reset()
|
v.cmdBuff.reset()
|
||||||
v.cmdBuff.setActive(true)
|
v.cmdBuff.setActive(true)
|
||||||
|
|
||||||
|
|
@ -227,13 +230,13 @@ func (v *tableView) setColorer(f colorerFn) {
|
||||||
|
|
||||||
// SetActions sets up keyboard action listener.
|
// SetActions sets up keyboard action listener.
|
||||||
func (v *tableView) setActions(aa keyActions) {
|
func (v *tableView) setActions(aa keyActions) {
|
||||||
v.tableMX.Lock()
|
// v.mx.Lock()
|
||||||
{
|
// {
|
||||||
for k, a := range aa {
|
for k, a := range aa {
|
||||||
v.actions[k] = a
|
v.actions[k] = a
|
||||||
}
|
|
||||||
}
|
}
|
||||||
v.tableMX.Unlock()
|
// }
|
||||||
|
// v.mx.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hints options
|
// Hints options
|
||||||
|
|
@ -251,16 +254,12 @@ func (v *tableView) refresh() {
|
||||||
|
|
||||||
// Update table content
|
// Update table content
|
||||||
func (v *tableView) update(data resource.TableData) {
|
func (v *tableView) update(data resource.TableData) {
|
||||||
v.refreshMX.Lock()
|
v.data = data
|
||||||
{
|
if !v.cmdBuff.empty() {
|
||||||
v.data = data
|
v.doUpdate(v.filtered())
|
||||||
if !v.cmdBuff.empty() {
|
} else {
|
||||||
v.doUpdate(v.filtered())
|
v.doUpdate(v.data)
|
||||||
} else {
|
|
||||||
v.doUpdate(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
v.refreshMX.Unlock()
|
|
||||||
v.resetTitle()
|
v.resetTitle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,7 +299,7 @@ func (v *tableView) sortIndicator(index int, name string) string {
|
||||||
if v.sortCol.asc {
|
if v.sortCol.asc {
|
||||||
order = "↑"
|
order = "↑"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s [green::]%s[::]", name, order)
|
return fmt.Sprintf("%s [%s::]%s[::]", name, v.app.styles.Style.Table.Header.SorterColor, order)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *tableView) doUpdate(data resource.TableData) {
|
func (v *tableView) doUpdate(data resource.TableData) {
|
||||||
|
|
@ -326,7 +325,11 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pads := make(maxyPad, len(data.Header))
|
pads := make(maxyPad, len(data.Header))
|
||||||
|
// v.mx.Lock()
|
||||||
|
// {
|
||||||
computeMaxColumns(pads, v.sortCol.index, data)
|
computeMaxColumns(pads, v.sortCol.index, data)
|
||||||
|
// }
|
||||||
|
// v.mx.Unlock()
|
||||||
var row int
|
var row int
|
||||||
for col, h := range data.Header {
|
for col, h := range data.Header {
|
||||||
v.addHeaderCell(col, h, pads)
|
v.addHeaderCell(col, h, pads)
|
||||||
|
|
@ -340,7 +343,7 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
||||||
prim, sec := v.sortAllRows(data.Rows, sortFn)
|
prim, sec := v.sortAllRows(data.Rows, sortFn)
|
||||||
for _, pk := range prim {
|
for _, pk := range prim {
|
||||||
for _, sk := range sec[pk] {
|
for _, sk := range sec[pk] {
|
||||||
fgColor := tcell.ColorGray
|
fgColor := config.AsColor(v.app.styles.Style.Table.FgColor)
|
||||||
if v.colorerFn != nil {
|
if v.colorerFn != nil {
|
||||||
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
||||||
}
|
}
|
||||||
|
|
@ -381,7 +384,8 @@ func (v *tableView) addHeaderCell(col int, name string, pads maxyPad) {
|
||||||
c := tview.NewTableCell(v.sortIndicator(col, name))
|
c := tview.NewTableCell(v.sortIndicator(col, name))
|
||||||
{
|
{
|
||||||
c.SetExpansion(1)
|
c.SetExpansion(1)
|
||||||
c.SetTextColor(tcell.ColorAntiqueWhite)
|
c.SetTextColor(config.AsColor(v.app.styles.Style.Table.Header.FgColor))
|
||||||
|
c.SetBackgroundColor(config.AsColor(v.app.styles.Style.Table.Header.BgColor))
|
||||||
}
|
}
|
||||||
v.SetCell(0, col, c)
|
v.SetCell(0, col, c)
|
||||||
}
|
}
|
||||||
|
|
@ -438,17 +442,28 @@ func (v *tableView) resetTitle() {
|
||||||
}
|
}
|
||||||
switch v.currentNS {
|
switch v.currentNS {
|
||||||
case resource.NotNamespaced, "*":
|
case resource.NotNamespaced, "*":
|
||||||
title = fmt.Sprintf(titleFmt, v.baseTitle, rc)
|
fmat := strings.Replace(titleFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[count", "["+v.app.styles.Style.Title.CounterColor, 1)
|
||||||
|
title = fmt.Sprintf(fmat, v.baseTitle, rc)
|
||||||
default:
|
default:
|
||||||
ns := v.currentNS
|
ns := v.currentNS
|
||||||
if v.currentNS == resource.AllNamespaces {
|
if v.currentNS == resource.AllNamespaces {
|
||||||
ns = resource.AllNamespace
|
ns = resource.AllNamespace
|
||||||
}
|
}
|
||||||
title = fmt.Sprintf(nsTitleFmt, v.baseTitle, ns, rc)
|
fmat := strings.Replace(nsTitleFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, -1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[hilite", "["+v.app.styles.Style.Title.HighlightColor, 1)
|
||||||
|
fmat = strings.Replace(fmat, "[count", "["+v.app.styles.Style.Title.CounterColor, 1)
|
||||||
|
title = fmt.Sprintf(fmat, v.baseTitle, ns, rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !v.cmdBuff.isActive() && !v.cmdBuff.empty() {
|
if !v.cmdBuff.isActive() && !v.cmdBuff.empty() {
|
||||||
title += fmt.Sprintf(searchFmt, v.cmdBuff)
|
fmat := strings.Replace(searchFmt, "[fg", "["+v.app.styles.Style.Title.FgColor, 1)
|
||||||
|
fmat = strings.Replace(fmat, ":bg:", ":"+v.app.styles.Style.Title.BgColor+":", -1)
|
||||||
|
fmat = strings.Replace(fmat, "[filter", "["+v.app.styles.Style.Title.FilterColor, 1)
|
||||||
|
|
||||||
|
title += fmt.Sprintf(fmat, v.cmdBuff)
|
||||||
}
|
}
|
||||||
v.SetTitle(title)
|
v.SetTitle(title)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -11,24 +13,39 @@ var (
|
||||||
keyRX = regexp.MustCompile(`\A(\s*)([\w|\-|\.|\s]+):\s*\z`)
|
keyRX = regexp.MustCompile(`\A(\s*)([\w|\-|\.|\s]+):\s*\z`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func colorizeYAML(raw string) string {
|
const (
|
||||||
|
yamlFullFmt = "%s[key::b]%s[colon::-]: [val::]%s"
|
||||||
|
yamlKeyFmt = "%s[key::b]%s[colon::-]:"
|
||||||
|
yamlValueFmt = "[val::]%s"
|
||||||
|
)
|
||||||
|
|
||||||
|
func colorizeYAML(style *config.Style, raw string) string {
|
||||||
lines := strings.Split(raw, "\n")
|
lines := strings.Split(raw, "\n")
|
||||||
|
|
||||||
|
fullFmt := strings.Replace(yamlFullFmt, "[key", "["+style.Yaml.KeyColor, 1)
|
||||||
|
fullFmt = strings.Replace(fullFmt, "[colon", "["+style.Yaml.ColonColor, 1)
|
||||||
|
fullFmt = strings.Replace(fullFmt, "[val", "["+style.Yaml.ValueColor, 1)
|
||||||
|
|
||||||
|
keyFmt := strings.Replace(yamlKeyFmt, "[key", "["+style.Yaml.KeyColor, 1)
|
||||||
|
keyFmt = strings.Replace(keyFmt, "[colon", "["+style.Yaml.ColonColor, 1)
|
||||||
|
|
||||||
|
valFmt := strings.Replace(yamlValueFmt, "[val", "["+style.Yaml.ValueColor, 1)
|
||||||
|
|
||||||
buff := make([]string, 0, len(lines))
|
buff := make([]string, 0, len(lines))
|
||||||
for _, l := range lines {
|
for _, l := range lines {
|
||||||
res := keyValRX.FindStringSubmatch(l)
|
res := keyValRX.FindStringSubmatch(l)
|
||||||
if len(res) == 4 {
|
if len(res) == 4 {
|
||||||
buff = append(buff, fmt.Sprintf("%s[steelblue::b]%s[white::-]: [papayawhip::]%s", res[1], res[2], res[3]))
|
buff = append(buff, fmt.Sprintf(fullFmt, res[1], res[2], res[3]))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
res = keyRX.FindStringSubmatch(l)
|
res = keyRX.FindStringSubmatch(l)
|
||||||
if len(res) == 3 {
|
if len(res) == 3 {
|
||||||
buff = append(buff, fmt.Sprintf("%s[steelblue::b]%s[white::-]:", res[1], res[2]))
|
buff = append(buff, fmt.Sprintf(keyFmt, res[1], res[2]))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
buff = append(buff, fmt.Sprintf("[papayawhip::]%s", l))
|
buff = append(buff, fmt.Sprintf(valFmt, l))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(buff, "\n")
|
return strings.Join(buff, "\n")
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package views
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,7 +41,8 @@ func TestYaml(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s, _ := config.NewStyles()
|
||||||
for _, u := range uu {
|
for _, u := range uu {
|
||||||
assert.Equal(t, u.e, colorizeYAML(u.s))
|
assert.Equal(t, u.e, colorizeYAML(s.Style, u.s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
k9s:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: black
|
||||||
|
logoColor: white
|
||||||
|
info:
|
||||||
|
fgColor: navajowhite
|
||||||
|
sectionColor: white
|
||||||
|
border:
|
||||||
|
fgColor: white
|
||||||
|
focusColor: white
|
||||||
|
menu:
|
||||||
|
fgColor: white
|
||||||
|
keyColor: white
|
||||||
|
numKeyColor: navajowhite
|
||||||
|
crumb:
|
||||||
|
fgColor: black
|
||||||
|
bgColor: navajowhite
|
||||||
|
activeColor: whitesmoke
|
||||||
|
table:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: black
|
||||||
|
cursorColor: white
|
||||||
|
header:
|
||||||
|
fgColor: darkgray
|
||||||
|
bgColor: black
|
||||||
|
sorterColor: white
|
||||||
|
status:
|
||||||
|
newColor: ghostwhite
|
||||||
|
modifyColor: navajowhite
|
||||||
|
addColor: darkslategray
|
||||||
|
errorColor: whitesmoke
|
||||||
|
highlightcolor: dimgray
|
||||||
|
killColor: slategray
|
||||||
|
completedColor: gray
|
||||||
|
title:
|
||||||
|
fgColor: ghostwhite
|
||||||
|
highlightColor: navajowhite
|
||||||
|
counterColor: navajowhite
|
||||||
|
filterColor: slategray
|
||||||
|
yaml:
|
||||||
|
keyColor: ghostwhite
|
||||||
|
colorColor: slategray
|
||||||
|
valueColor: navajowhite
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
k9s:
|
||||||
|
fgColor: dodgerblue
|
||||||
|
bgColor: white
|
||||||
|
logoColor: blue
|
||||||
|
info:
|
||||||
|
fgColor: lightskyblue
|
||||||
|
sectionColor: steelblue
|
||||||
|
border:
|
||||||
|
fgColor: dodgerblue
|
||||||
|
focusColor: aliceblue
|
||||||
|
menu:
|
||||||
|
fgColor: darkblue
|
||||||
|
keyColor: cornflowerblue
|
||||||
|
numKeyColor: cadetblue
|
||||||
|
crumb:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: steelblue
|
||||||
|
activeColor: skyblue
|
||||||
|
table:
|
||||||
|
fgColor: blue
|
||||||
|
bgColor: darkblue
|
||||||
|
cursorColor: aqua
|
||||||
|
header:
|
||||||
|
fgColor: white
|
||||||
|
bgColor: darkblue
|
||||||
|
sorterColor: orange
|
||||||
|
status:
|
||||||
|
newColor: blue
|
||||||
|
modifyColor: powderblue
|
||||||
|
addColor: lightskyblue
|
||||||
|
errorColor: indianred
|
||||||
|
highlightcolor: royalblue
|
||||||
|
killColor: slategray
|
||||||
|
completedColor: gray
|
||||||
|
title:
|
||||||
|
fgColor: aqua
|
||||||
|
bgColor: white
|
||||||
|
highlightColor: skyblue
|
||||||
|
counterColor: slateblue
|
||||||
|
filterColor: slategray
|
||||||
|
yaml:
|
||||||
|
keyColor: steelblue
|
||||||
|
colorColor: blue
|
||||||
|
valueColor: royalblue
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
k9s:
|
||||||
|
fgColor: dodgerblue
|
||||||
|
bgColor: black
|
||||||
|
logoColor: orange
|
||||||
|
info:
|
||||||
|
fgColor: white
|
||||||
|
sectionColor: dodgerblue
|
||||||
|
border:
|
||||||
|
fgColor: dodgerblue
|
||||||
|
focusColor: aqua
|
||||||
|
menu:
|
||||||
|
fgColor: white
|
||||||
|
keyColor: dodgerblue
|
||||||
|
numKeyColor: fuchsia
|
||||||
|
crumb:
|
||||||
|
fgColor: black
|
||||||
|
bgColor: steelblue
|
||||||
|
activeColor: orange
|
||||||
|
table:
|
||||||
|
fgColor: blue
|
||||||
|
bgColor: black
|
||||||
|
cursorColor: aqua
|
||||||
|
header:
|
||||||
|
fgColor: aqua
|
||||||
|
bgColor: black
|
||||||
|
sorterColor: orange
|
||||||
|
yaml:
|
||||||
|
keyColor: steelblue
|
||||||
|
colonColor: white
|
||||||
|
valueColor: papayawhip
|
||||||
Loading…
Reference in New Issue