mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-26 06:18:04 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2ddb94c16 | ||
|
|
861de33bfe | ||
|
|
26d82dd5ab | ||
|
|
b1294cbe1a | ||
|
|
d4583a222e | ||
|
|
a0bf59edc6 | ||
|
|
79a79979e2 | ||
|
|
76e4fe51b5 | ||
|
|
95ea5e4d6d | ||
|
|
c139f3941d | ||
|
|
80022c3ef8 | ||
|
|
a07cfcdbb4 | ||
|
|
8826124f70 | ||
|
|
703b4029de | ||
|
|
a2dc1ed96b | ||
|
|
29c1682bbb | ||
|
|
c7d18a7fb7 | ||
|
|
e9ee10287b | ||
|
|
57d4d073e9 | ||
|
|
47dae4d35a | ||
|
|
0ac8eec368 | ||
|
|
aec46d43f7 | ||
|
|
37e1d44bf1 | ||
|
|
362cb09e6d | ||
|
|
209f5b5e44 | ||
|
|
a0680a4820 | ||
|
|
d95cac94d5 | ||
|
|
bbb425bfea | ||
|
|
679d31e4ab |
18
README.md
18
README.md
@@ -9,7 +9,7 @@
|
|||||||
<p align="center">A simplified way of working with Helm.</p>
|
<p align="center">A simplified way of working with Helm.</p>
|
||||||
|
|
||||||
|
|
||||||
 [](https://github.com/komodorio/helm-dashboard/issues)      [](https://github.com/komodorio/helm-dashboard)
|
 [](https://github.com/komodorio/helm-dashboard/issues)    [](https://github.com/komodorio/helm-dashboard/releases)  [](https://github.com/komodorio/helm-dashboard)
|
||||||
|
|
||||||
<kbd>[<img src="images/screenshot.png" style="width: 100%; border: 1px solid silver;" border="1" alt="Screenshot">](images/screenshot.png)</kbd>
|
<kbd>[<img src="images/screenshot.png" style="width: 100%; border: 1px solid silver;" border="1" alt="Screenshot">](images/screenshot.png)</kbd>
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ For all the release(s) (installed helm charts), you can execute helm tests for t
|
|||||||
You can execute `helm test` for the specific release as below:
|
You can execute `helm test` for the specific release as below:
|
||||||

|

|
||||||
|
|
||||||
The result of executed `helm test` for the release will be disapled as below:
|
The result of executed `helm test` for the release will be displayed as below:
|
||||||

|

|
||||||
|
|
||||||
### Scanner Integrations
|
### Scanner Integrations
|
||||||
@@ -151,15 +151,25 @@ Kindly read our [Contributing Guide](CONTRIBUTING.md) to learn and understand ab
|
|||||||
|
|
||||||
## Local Dev Testing
|
## Local Dev Testing
|
||||||
|
|
||||||
Prerequisites: `helm` and `kubectl` binaries installed and operational.
|
Prerequisites, binaries installed and operational:
|
||||||
|
|
||||||
|
- [Go](https://go.dev/doc/install)
|
||||||
|
|
||||||
There is a need to build binary for plugin to function, run:
|
There is a need to build binary for plugin to function, run:
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go build -o bin/dashboard .
|
go build -o bin/dashboard .
|
||||||
```
|
```
|
||||||
|
|
||||||
You can just run the `bin/dashboard` binary directly, it will just work.
|
### Windows
|
||||||
|
|
||||||
|
```bat
|
||||||
|
go build -o bin\dashboard.exe .
|
||||||
|
```
|
||||||
|
|
||||||
|
You can just run the `dashboard` or `dashboard.exe` binary directly, it will just work.
|
||||||
|
|
||||||
To install, checkout the source code and run from source dir:
|
To install, checkout the source code and run from source dir:
|
||||||
|
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ name: helm-dashboard
|
|||||||
description: A GUI Dashboard for Helm by Komodor
|
description: A GUI Dashboard for Helm by Komodor
|
||||||
icon: "https://raw.githubusercontent.com/komodorio/helm-dashboard/main/pkg/dashboard/static/logo.svg"
|
icon: "https://raw.githubusercontent.com/komodorio/helm-dashboard/main/pkg/dashboard/static/logo.svg"
|
||||||
|
|
||||||
version: 0.1.4
|
version: 0.1.7
|
||||||
appVersion: "1.1.0"
|
appVersion: "1.3.0"
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ spec:
|
|||||||
{{- if .Values.dashboard.persistence.hostPath }}
|
{{- if .Values.dashboard.persistence.hostPath }}
|
||||||
storageClassName: ""
|
storageClassName: ""
|
||||||
{{- else }}
|
{{- else }}
|
||||||
|
{{- if kindIs "string" .Values.dashboard.persistence.storageClass }}
|
||||||
storageClassName: "{{ .Values.dashboard.persistence.storageClass }}"
|
storageClassName: "{{ .Values.dashboard.persistence.storageClass }}"
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
accessModes:
|
accessModes:
|
||||||
{{- if not (empty .Values.dashboard.persistence.accessModes) }}
|
{{- if not (empty .Values.dashboard.persistence.accessModes) }}
|
||||||
{{- range .Values.dashboard.persistence.accessModes }}
|
{{- range .Values.dashboard.persistence.accessModes }}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ dashboard:
|
|||||||
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
## set, choosing the default provisioner. (gp2 on AWS, standard on
|
||||||
## GKE, AWS & OpenStack)
|
## GKE, AWS & OpenStack)
|
||||||
##
|
##
|
||||||
storageClass: ""
|
storageClass: null
|
||||||
|
|
||||||
## Helm Dashboard Persistent Volume access modes
|
## Helm Dashboard Persistent Volume access modes
|
||||||
## Must match those of existing PV or dynamic provisioner
|
## Must match those of existing PV or dynamic provisioner
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -48,7 +48,7 @@ require (
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/docker/cli v20.10.21+incompatible // indirect
|
github.com/docker/cli v20.10.21+incompatible // indirect
|
||||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||||
github.com/docker/docker v20.10.21+incompatible // indirect
|
github.com/docker/docker v20.10.24+incompatible // indirect
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
github.com/docker/docker-credential-helpers v0.7.0 // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
github.com/docker/go-connections v0.4.0 // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
@@ -61,7 +61,7 @@ require (
|
|||||||
github.com/fvbommel/sortorder v1.0.1 // indirect
|
github.com/fvbommel/sortorder v1.0.1 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-errors/errors v1.0.1 // indirect
|
github.com/go-errors/errors v1.0.1 // indirect
|
||||||
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
|
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
@@ -96,7 +96,7 @@ require (
|
|||||||
github.com/lib/pq v1.10.7 // indirect
|
github.com/lib/pq v1.10.7 // indirect
|
||||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||||
github.com/mailru/easyjson v0.7.6 // indirect
|
github.com/mailru/easyjson v0.7.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
|
|||||||
19
go.sum
19
go.sum
@@ -135,8 +135,8 @@ github.com/docker/cli v20.10.21+incompatible h1:qVkgyYUnOLQ98LtXBrwd/duVqPT2X4SH
|
|||||||
github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v20.10.21+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68=
|
||||||
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v20.10.21+incompatible h1:UTLdBmHk3bEY+w8qeO5KttOhy6OmXWsl/FEet9Uswog=
|
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
|
||||||
github.com/docker/docker v20.10.21+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v20.10.24+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
|
||||||
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||||
@@ -195,8 +195,9 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm
|
|||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4=
|
|
||||||
github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
|
github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY=
|
||||||
|
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
||||||
|
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
||||||
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-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||||
@@ -424,8 +425,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
|||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
@@ -456,8 +457,8 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI=
|
|||||||
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
@@ -468,8 +469,8 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
|
|||||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
|
||||||
@@ -555,8 +556,8 @@ github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77
|
|||||||
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=
|
||||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||||
github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg=
|
|
||||||
github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU=
|
||||||
|
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
||||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||||
@@ -665,7 +666,6 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
|
|||||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI=
|
||||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE=
|
||||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
@@ -882,7 +882,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
|||||||
14
main.go
14
main.go
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/joomcode/errorx"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -34,6 +35,11 @@ type options struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
err := os.Setenv("HD_VERSION", version) // for anyone willing to access it
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Failed to remember app version because of error: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
opts := parseFlags()
|
opts := parseFlags()
|
||||||
if opts.BindHost == "" {
|
if opts.BindHost == "" {
|
||||||
host := os.Getenv("HD_BIND")
|
host := os.Getenv("HD_BIND")
|
||||||
@@ -68,7 +74,13 @@ func main() {
|
|||||||
|
|
||||||
address, webServerDone, err := server.StartServer(ctx, cancel)
|
address, webServerDone, err := server.StartServer(ctx, cancel)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to start Helm Dashboard: %+v", err)
|
if errorx.IsOfType(err, errorx.InitializationFailed) {
|
||||||
|
log.Debugf("Full error: %+v", err)
|
||||||
|
log.Errorf("No Kubernetes cluster connection possible. Make sure you have valid kubeconfig file or run dashboard from inside cluster. See https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/")
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
log.Fatalf("Failed to start Helm Dashboard: %+v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.NoTracking {
|
if !opts.NoTracking {
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
|
"net/http"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/hexops/gotextdiff"
|
"github.com/hexops/gotextdiff"
|
||||||
"github.com/hexops/gotextdiff/myers"
|
"github.com/hexops/gotextdiff/myers"
|
||||||
@@ -18,10 +26,6 @@ import (
|
|||||||
"helm.sh/helm/v3/pkg/repo"
|
"helm.sh/helm/v3/pkg/repo"
|
||||||
helmtime "helm.sh/helm/v3/pkg/time"
|
helmtime "helm.sh/helm/v3/pkg/time"
|
||||||
"k8s.io/utils/strings/slices"
|
"k8s.io/utils/strings/slices"
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HelmHandler struct {
|
type HelmHandler struct {
|
||||||
@@ -130,8 +134,39 @@ func (h *HelmHandler) Resources(c *gin.Context) {
|
|||||||
|
|
||||||
res, err := objects.ParseManifests(rel.Orig.Manifest)
|
res, err := objects.ParseManifests(rel.Orig.Manifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
res = append(res, &v1.Carp{
|
||||||
return
|
TypeMeta: metav1.TypeMeta{Kind: "ManifestParseError"},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: err.Error(),
|
||||||
|
},
|
||||||
|
Spec: v1.CarpSpec{},
|
||||||
|
Status: v1.CarpStatus{
|
||||||
|
Phase: "BrokenManifest",
|
||||||
|
Message: err.Error(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
//_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
//return
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Query("health") != "" { // we need to query k8s for health status
|
||||||
|
app := h.GetApp(c)
|
||||||
|
if app == nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, obj := range res {
|
||||||
|
ns := obj.Namespace
|
||||||
|
if ns == "" {
|
||||||
|
ns = c.Param("ns")
|
||||||
|
}
|
||||||
|
info, err := app.K8s.GetResourceInfo(obj.Kind, ns, obj.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to get resource info for %s %s/%s: %+v", obj.Name, ns, obj.Name, err)
|
||||||
|
info = &v1.Carp{}
|
||||||
|
}
|
||||||
|
obj.Status = *EnhanceStatus(info, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, res)
|
c.IndentedJSON(http.StatusOK, res)
|
||||||
@@ -207,7 +242,21 @@ func (h *HelmHandler) RepoLatestVer(c *gin.Context) {
|
|||||||
if len(res) > 0 {
|
if len(res) > 0 {
|
||||||
c.IndentedJSON(http.StatusOK, res[:1])
|
c.IndentedJSON(http.StatusOK, res[:1])
|
||||||
} else {
|
} else {
|
||||||
c.Status(http.StatusNoContent)
|
// caching it to avoid too many requests
|
||||||
|
found, err := h.Data.Cache.String("chart-artifacthub-query/"+qp.Name, nil, func() (string, error) {
|
||||||
|
return h.repoFromArtifactHub(qp.Name)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if found == "" {
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
} else {
|
||||||
|
c.Header("Content-Type", "application/json")
|
||||||
|
c.String(http.StatusOK, found)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +284,6 @@ func (h *HelmHandler) RepoCharts(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: enrich with installed
|
|
||||||
enrichRepoChartsWithInstalled(charts, installed)
|
enrichRepoChartsWithInstalled(charts, installed)
|
||||||
|
|
||||||
sort.Slice(charts, func(i, j int) bool {
|
sort.Slice(charts, func(i, j int) bool {
|
||||||
@@ -480,7 +528,7 @@ func (h *HelmHandler) RepoAdd(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: more repo options to accept
|
// TODO: more repo options to accept
|
||||||
err := app.Repositories.Add(c.PostForm("name"), c.PostForm("url"))
|
err := app.Repositories.Add(c.PostForm("name"), c.PostForm("url"), c.PostForm("username"), c.PostForm("password"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
@@ -553,6 +601,62 @@ func (h *HelmHandler) handleGetSection(rel *objects.Release, section string, rDi
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *HelmHandler) repoFromArtifactHub(name string) (string, error) {
|
||||||
|
results, err := objects.QueryArtifactHub(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Failed to query ArtifactHub: %s", err)
|
||||||
|
return "", nil // swallowing the error to not annoy users
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(results) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(results, func(i, j int) bool {
|
||||||
|
ri, rj := results[i], results[j]
|
||||||
|
|
||||||
|
// we prefer official repos
|
||||||
|
if ri.Repository.Official && !rj.Repository.Official {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// more popular
|
||||||
|
if ri.Stars != rj.Stars {
|
||||||
|
return ri.Stars > rj.Stars
|
||||||
|
}
|
||||||
|
|
||||||
|
// or from verified publishers
|
||||||
|
if ri.Repository.VerifiedPublisher && !rj.Repository.VerifiedPublisher {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// or with more recent app version
|
||||||
|
c := semver.Compare("v"+ri.AppVersion, "v"+rj.AppVersion)
|
||||||
|
if c != 0 {
|
||||||
|
return c > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// shorter repo name is usually closer to officials
|
||||||
|
return len(ri.Repository.Name) < len(rj.Repository.Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
r := results[0]
|
||||||
|
buf, err := json.Marshal([]*RepoChartElement{{
|
||||||
|
Name: r.Name,
|
||||||
|
Version: r.Version,
|
||||||
|
AppVersion: r.AppVersion,
|
||||||
|
Description: r.Description,
|
||||||
|
Repository: r.Repository.Name,
|
||||||
|
URLs: []string{r.Repository.Url},
|
||||||
|
IsSuggestedRepo: true,
|
||||||
|
}})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
type RepoChartElement struct { // TODO: do we need it at all? there is existing repo.ChartVersion in Helm
|
type RepoChartElement struct { // TODO: do we need it at all? there is existing repo.ChartVersion in Helm
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
@@ -563,6 +667,7 @@ type RepoChartElement struct { // TODO: do we need it at all? there is existing
|
|||||||
InstalledName string `json:"installed_name"`
|
InstalledName string `json:"installed_name"`
|
||||||
Repository string `json:"repository"`
|
Repository string `json:"repository"`
|
||||||
URLs []string `json:"urls"`
|
URLs []string `json:"urls"`
|
||||||
|
IsSuggestedRepo bool `json:"isSuggestedRepo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HReleaseToJSON(o *release.Release) *ReleaseElement {
|
func HReleaseToJSON(o *release.Release) *ReleaseElement {
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/joomcode/errorx"
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/joomcode/errorx"
|
||||||
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
"github.com/komodorio/helm-dashboard/pkg/dashboard/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v12 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
|
"k8s.io/utils/strings/slices"
|
||||||
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const Unknown = "Unknown"
|
||||||
|
const Healthy = "Healthy"
|
||||||
|
const Unhealthy = "Unhealthy"
|
||||||
|
const Progressing = "Progressing"
|
||||||
|
|
||||||
type KubeHandler struct {
|
type KubeHandler struct {
|
||||||
*Contexted
|
*Contexted
|
||||||
}
|
}
|
||||||
@@ -50,25 +56,67 @@ func (h *KubeHandler) GetResourceInfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
EnhanceStatus(res)
|
EnhanceStatus(res, nil)
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusOK, res)
|
c.IndentedJSON(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EnhanceStatus(res *v12.Carp) {
|
func EnhanceStatus(res *v12.Carp, err error) *v12.CarpStatus {
|
||||||
// custom logic to provide most meaningful status for the resource
|
s := res.Status
|
||||||
if res.Status.Phase == "Active" || res.Status.Phase == "Error" {
|
if s.Conditions == nil {
|
||||||
_ = res.Name + ""
|
s.Conditions = []v12.CarpCondition{}
|
||||||
} else if res.Status.Phase == "" && len(res.Status.Conditions) > 0 {
|
|
||||||
res.Status.Phase = v12.CarpPhase(res.Status.Conditions[len(res.Status.Conditions)-1].Type)
|
|
||||||
res.Status.Message = res.Status.Conditions[len(res.Status.Conditions)-1].Message
|
|
||||||
res.Status.Reason = res.Status.Conditions[len(res.Status.Conditions)-1].Reason
|
|
||||||
if res.Status.Conditions[len(res.Status.Conditions)-1].Status == "False" {
|
|
||||||
res.Status.Phase = "Not" + res.Status.Phase
|
|
||||||
}
|
|
||||||
} else if res.Status.Phase == "" {
|
|
||||||
res.Status.Phase = "Exists"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c := v12.CarpCondition{
|
||||||
|
Type: "hdHealth",
|
||||||
|
Status: Unknown,
|
||||||
|
Reason: s.Reason,
|
||||||
|
Message: s.Message,
|
||||||
|
}
|
||||||
|
|
||||||
|
// custom logic to provide most meaningful status for the resource
|
||||||
|
if err != nil {
|
||||||
|
c.Reason = "ErrorGettingStatus"
|
||||||
|
c.Message = err.Error()
|
||||||
|
} else if s.Phase == "Error" {
|
||||||
|
c.Status = Unhealthy
|
||||||
|
} else if slices.Contains([]string{"Available", "Active", "Established", "Bound", "Ready"}, string(s.Phase)) {
|
||||||
|
c.Status = Healthy
|
||||||
|
} else if s.Phase == "" && len(s.Conditions) > 0 {
|
||||||
|
for _, cond := range s.Conditions {
|
||||||
|
if cond.Type == "Progressing" { // https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
|
||||||
|
if cond.Status == "False" {
|
||||||
|
c.Status = Unhealthy
|
||||||
|
c.Reason = cond.Reason
|
||||||
|
c.Message = cond.Message
|
||||||
|
} else if cond.Reason != "NewReplicaSetAvailable" {
|
||||||
|
c.Status = Progressing
|
||||||
|
c.Reason = cond.Reason
|
||||||
|
c.Message = cond.Message
|
||||||
|
}
|
||||||
|
} else if cond.Type == "Available" && c.Status == Unknown {
|
||||||
|
if cond.Status == "False" {
|
||||||
|
c.Status = Unhealthy
|
||||||
|
} else {
|
||||||
|
c.Status = Healthy
|
||||||
|
}
|
||||||
|
c.Reason = cond.Reason
|
||||||
|
c.Message = cond.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if s.Phase == "Pending" {
|
||||||
|
c.Status = Progressing
|
||||||
|
c.Reason = string(s.Phase)
|
||||||
|
} else if s.Phase == "" {
|
||||||
|
c.Status = Healthy
|
||||||
|
c.Reason = "Exists"
|
||||||
|
} else {
|
||||||
|
log.Warnf("Unhandled status: %v", s)
|
||||||
|
c.Reason = string(s.Phase)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Conditions = append(s.Conditions, c)
|
||||||
|
return &s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *KubeHandler) Describe(c *gin.Context) {
|
func (h *KubeHandler) Describe(c *gin.Context) {
|
||||||
|
|||||||
90
pkg/dashboard/objects/artifacthub.go
Normal file
90
pkg/dashboard/objects/artifacthub.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package objects
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
neturl "net/url"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mxArtifactHub sync.Mutex
|
||||||
|
|
||||||
|
func QueryArtifactHub(chartName string) ([]*ArtifactHubResult, error) {
|
||||||
|
mxArtifactHub.Lock() // to avoid parallel request spike
|
||||||
|
defer mxArtifactHub.Unlock()
|
||||||
|
|
||||||
|
url := os.Getenv("HD_ARTIFACT_HUB_URL")
|
||||||
|
if url == "" {
|
||||||
|
url = "https://artifacthub.io/api/v1/packages/search"
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := neturl.Parse(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.RawQuery = "offset=0&limit=5&facets=false&kind=0&deprecated=false&sort=relevance&ts_query_web=" + neturl.QueryEscape(chartName)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", p.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", "Komodor Helm Dashboard/"+os.Getenv("HD_VERSION")) // TODO
|
||||||
|
|
||||||
|
log.Debugf("Making HTTP request: %v", req)
|
||||||
|
res, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
|
||||||
|
if res.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("failed to fetch %s : %s", p.String(), res.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := ArtifactHubResults{}
|
||||||
|
|
||||||
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.Packages, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubResults struct {
|
||||||
|
Packages []*ArtifactHubResult `json:"packages"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubResult struct {
|
||||||
|
PackageId string `json:"package_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
NormalizedName string `json:"normalized_name"`
|
||||||
|
LogoImageId string `json:"logo_image_id"`
|
||||||
|
Stars int `json:"stars"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
AppVersion string `json:"app_version"`
|
||||||
|
Deprecated bool `json:"deprecated"`
|
||||||
|
Signed bool `json:"signed"`
|
||||||
|
ProductionOrganizationsCount int `json:"production_organizations_count"`
|
||||||
|
Ts int `json:"ts"`
|
||||||
|
Repository ArtifactHubRepo `json:"repository"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ArtifactHubRepo struct {
|
||||||
|
Url string `json:"url"`
|
||||||
|
Kind int `json:"kind"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Official bool `json:"official"`
|
||||||
|
DisplayName string `json:"display_name"`
|
||||||
|
RepositoryId string `json:"repository_id"`
|
||||||
|
ScannerDisabled bool `json:"scanner_disabled"`
|
||||||
|
OrganizationName string `json:"organization_name"`
|
||||||
|
VerifiedPublisher bool `json:"verified_publisher"`
|
||||||
|
OrganizationDisplayName string `json:"organization_display_name"`
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ import (
|
|||||||
"helm.sh/helm/v3/pkg/release"
|
"helm.sh/helm/v3/pkg/release"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
//"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DataLayer struct {
|
type DataLayer struct {
|
||||||
@@ -112,20 +114,20 @@ func ParseManifests(out string) ([]*v1.Carp, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.Decorate(err, "failed to parse manifest document #%d", len(res)+1)
|
return res, errorx.Decorate(err, "failed to parse manifest document #%d", len(res)+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// k8s libs uses only JSON tags defined, say hello to https://github.com/go-yaml/yaml/issues/424
|
// k8s libs uses only JSON tags defined, say hello to https://github.com/go-yaml/yaml/issues/424
|
||||||
// we can juggle it
|
// we can juggle it
|
||||||
jsoned, err := json.Marshal(tmp)
|
jsoned, err := json.Marshal(tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var doc v1.Carp
|
var doc v1.Carp
|
||||||
err = json.Unmarshal(jsoned, &doc)
|
err = json.Unmarshal(jsoned, &doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if doc.Kind == "" {
|
if doc.Kind == "" {
|
||||||
@@ -194,13 +196,12 @@ func (d *DataLayer) nsForCtx(ctx string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataLayer) PeriodicTasks(ctx context.Context) {
|
func (d *DataLayer) PeriodicTasks(ctx context.Context) {
|
||||||
if !d.StatusInfo.ClusterMode { // TODO: maybe have a separate flag for that?
|
// TODO: separate scanning setup for in-cluster?
|
||||||
log.Debugf("Not in cluster mode, not starting background tasks")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// auto-update repos
|
if os.Getenv("HD_NO_AUTOUPDATE") == "" {
|
||||||
go d.loopUpdateRepos(ctx, 10*time.Minute) // TODO: parameterize interval?
|
// auto-update repos
|
||||||
|
go d.loopUpdateRepos(ctx, 10*time.Minute) // TODO: parameterize interval?
|
||||||
|
}
|
||||||
|
|
||||||
// auto-scan
|
// auto-scan
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,7 +126,14 @@ func (k *K8s) DescribeResource(kind string, ns string, name string) (string, err
|
|||||||
|
|
||||||
func (k *K8s) GetResource(kind string, namespace string, name string) (*runtime.Object, error) {
|
func (k *K8s) GetResource(kind string, namespace string, name string) (*runtime.Object, error) {
|
||||||
builder := k.Factory.NewBuilder()
|
builder := k.Factory.NewBuilder()
|
||||||
resp := builder.Unstructured().NamespaceParam(namespace).Flatten().ResourceNames(kind, name).Do()
|
builder = builder.Unstructured().SingleResourceType()
|
||||||
|
if namespace != "" {
|
||||||
|
builder = builder.NamespaceParam(namespace)
|
||||||
|
} else {
|
||||||
|
builder = builder.DefaultNamespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := builder.Flatten().ResourceNames(kind, name).Do()
|
||||||
if resp.Err() != nil {
|
if resp.Err() != nil {
|
||||||
return nil, errorx.Decorate(resp.Err(), "failed to get k8s resource")
|
return nil, errorx.Decorate(resp.Err(), "failed to get k8s resource")
|
||||||
}
|
}
|
||||||
@@ -139,6 +146,7 @@ func (k *K8s) GetResource(kind string, namespace string, name string) (*runtime.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (k *K8s) GetResourceInfo(kind string, namespace string, name string) (*testapiv1.Carp, error) {
|
func (k *K8s) GetResourceInfo(kind string, namespace string, name string) (*testapiv1.Carp, error) {
|
||||||
|
// TODO: mutex to avoid a lot of requests?
|
||||||
obj, err := k.GetResource(kind, namespace, name)
|
obj, err := k.GetResource(kind, namespace, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errorx.Decorate(err, "failed to get k8s object")
|
return nil, errorx.Decorate(err, "failed to get k8s object")
|
||||||
|
|||||||
@@ -65,11 +65,15 @@ func (r *Repositories) List() ([]Repository, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repositories) Add(name string, url string) error {
|
func (r *Repositories) Add(name string, url string, username string, password string) error {
|
||||||
if name == "" || url == "" {
|
if name == "" || url == "" {
|
||||||
return errors.New("Name and URL are required parameters to add the repository")
|
return errors.New("Name and URL are required parameters to add the repository")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (username != "" && password == "") || (username == "" && password != "") {
|
||||||
|
return errors.New("Username and Password, both are required parameters to add the repository with authentication")
|
||||||
|
}
|
||||||
|
|
||||||
// copied from cmd/helm/repo_add.go
|
// copied from cmd/helm/repo_add.go
|
||||||
repoFile := r.Settings.RepositoryConfig
|
repoFile := r.Settings.RepositoryConfig
|
||||||
|
|
||||||
@@ -88,10 +92,10 @@ func (r *Repositories) Add(name string, url string) error {
|
|||||||
defer r.mx.Unlock()
|
defer r.mx.Unlock()
|
||||||
|
|
||||||
c := repo.Entry{
|
c := repo.Entry{
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: url,
|
URL: url,
|
||||||
//Username: o.username,
|
Username: username,
|
||||||
//Password: o.password,
|
Password: password,
|
||||||
//PassCredentialsAll: o.passCredentialsAll,
|
//PassCredentialsAll: o.passCredentialsAll,
|
||||||
//CertFile: o.certFile,
|
//CertFile: o.certFile,
|
||||||
//KeyFile: o.keyFile,
|
//KeyFile: o.keyFile,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ func TestFlow(t *testing.T) {
|
|||||||
testRepoUrl := "https://helm.github.io/examples"
|
testRepoUrl := "https://helm.github.io/examples"
|
||||||
|
|
||||||
// add repo
|
// add repo
|
||||||
err = testRepository.Add(testRepoName, testRepoUrl)
|
err = testRepository.Add(testRepoName, testRepoUrl, "", "")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
// get repo
|
// get repo
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (s *Server) StartServer(ctx context.Context, cancel context.CancelFunc) (st
|
|||||||
|
|
||||||
err = s.detectClusterMode(data)
|
err = s.detectClusterMode(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, errorx.Decorate(err, "Failed to detect cluster mode")
|
||||||
}
|
}
|
||||||
|
|
||||||
go checkUpgrade(data.StatusInfo)
|
go checkUpgrade(data.StatusInfo)
|
||||||
@@ -80,7 +80,7 @@ func (s *Server) detectClusterMode(data *objects.DataLayer) error {
|
|||||||
}
|
}
|
||||||
ns, err := app.K8s.GetNameSpaces()
|
ns, err := app.K8s.GetNameSpaces()
|
||||||
if err != nil { // no point in continuing without kubectl context and k8s connection
|
if err != nil { // no point in continuing without kubectl context and k8s connection
|
||||||
return err
|
return errorx.InitializationFailed.Wrap(err, "No k8s cluster connection")
|
||||||
}
|
}
|
||||||
log.Debugf("Got %d namespaces listed", len(ns.Items))
|
log.Debugf("Got %d namespaces listed", len(ns.Items))
|
||||||
data.StatusInfo.ClusterMode = true
|
data.StatusInfo.ClusterMode = true
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ function checkUpgradeable(name) {
|
|||||||
if (!data || !data.length) {
|
if (!data || !data.length) {
|
||||||
btnUpgradeCheck.prop("disabled", true)
|
btnUpgradeCheck.prop("disabled", true)
|
||||||
btnUpgradeCheck.text("")
|
btnUpgradeCheck.text("")
|
||||||
$("#btnAddRepository").text("Add repository for it")
|
$("#btnAddRepository").text("Add repository for it").data("suggestRepo", "")
|
||||||
|
} else if (data[0].isSuggestedRepo) {
|
||||||
|
btnUpgradeCheck.prop("disabled", true)
|
||||||
|
btnUpgradeCheck.text("")
|
||||||
|
$("#btnAddRepository").text("Add repository for it: "+data[0].repository).data("suggestRepo", data[0].repository).data("suggestRepoUrl", data[0].urls[0])
|
||||||
} else {
|
} else {
|
||||||
$("#btnAddRepository").text("")
|
$("#btnAddRepository").text("")
|
||||||
btnUpgradeCheck.text("Check for new version")
|
btnUpgradeCheck.text("Check for new version")
|
||||||
@@ -399,7 +403,12 @@ $("#btnRollback").click(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("#btnAddRepository").click(function () {
|
$("#btnAddRepository").click(function () {
|
||||||
|
const self=$(this)
|
||||||
setHashParam("section", "repository")
|
setHashParam("section", "repository")
|
||||||
|
if (self.data("suggestRepo")) {
|
||||||
|
setHashParam("suggestRepo", self.data("suggestRepo"))
|
||||||
|
setHashParam("suggestRepoUrl", self.data("suggestRepoUrl"))
|
||||||
|
}
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,28 @@
|
|||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
const TRACK_EVENT_TYPE = "track"
|
||||||
|
const IDENTIFY_EVENT_TYPE = "identify"
|
||||||
|
const BASE_ANALYTIC_MSG = {
|
||||||
|
method: "POST",
|
||||||
|
mode: "cors",
|
||||||
|
cache: "no-cache",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"api-key": "komodor.analytics@admin.com",
|
||||||
|
},
|
||||||
|
redirect: "follow",
|
||||||
|
referrerPolicy: "no-referrer"
|
||||||
|
}
|
||||||
xhr.onload = function () {
|
xhr.onload = function () {
|
||||||
|
|
||||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
const status = JSON.parse(xhr.responseText);
|
const status = JSON.parse(xhr.responseText);
|
||||||
const version = status.CurVer
|
const version = status.CurVer
|
||||||
if (status.Analytics) {
|
if (status.Analytics) {
|
||||||
enableDD(version)
|
enableDD(version)
|
||||||
enableHeap(version, status.ClusterMode)
|
enableHeap(version, status.ClusterMode)
|
||||||
|
enableSegmentBackend(version, status.ClusterMode)
|
||||||
|
} else {
|
||||||
|
console.log("Analytics is disabled in this session")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,12 +75,57 @@ function enableHeap(version, inCluster) {
|
|||||||
heap.load("4249623943");
|
heap.load("4249623943");
|
||||||
window.heap.addEventProperties({
|
window.heap.addEventProperties({
|
||||||
'version': version,
|
'version': version,
|
||||||
'installationMode': inCluster?"cluster":"local"
|
'installationMode': inCluster ? "cluster" : "local"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendStats(name, prop){
|
function sendStats(name, prop) {
|
||||||
if (window.heap) {
|
if (window.heap) {
|
||||||
window.heap.track(name, prop);
|
window.heap.track(name, prop);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enableSegmentBackend(version, ClusterMode) {
|
||||||
|
sendToSegmentThroughAPI("helm dashboard loaded", {version, 'installationMode': ClusterMode ? "cluster" : "local"}, TRACK_EVENT_TYPE)
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToSegmentThroughAPI(eventName, properties, segmentCallType) {
|
||||||
|
const userId = getUserId();
|
||||||
|
try {
|
||||||
|
sendData(properties, segmentCallType, userId, eventName);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("failed sending data to segment", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendData(data, eventType, userId, eventName) {
|
||||||
|
const body = createBody(eventType, userId, data, eventName);
|
||||||
|
return fetch(`https://api.komodor.com/analytics/segment/${eventType}`, {
|
||||||
|
...BASE_ANALYTIC_MSG,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createBody(segmentCallType, userId, params, eventName) {
|
||||||
|
const data = {userId: userId};
|
||||||
|
if (segmentCallType === IDENTIFY_EVENT_TYPE) {
|
||||||
|
data["traits"] = params;
|
||||||
|
} else if (segmentCallType === TRACK_EVENT_TYPE) {
|
||||||
|
if (!eventName) {
|
||||||
|
throw new Error("no eventName parameter on segment track call");
|
||||||
|
}
|
||||||
|
data["properties"] = params;
|
||||||
|
data["eventName"] = eventName;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserId = (() => {
|
||||||
|
let userId = null;
|
||||||
|
return () => {
|
||||||
|
if (!userId) {
|
||||||
|
userId = crypto.randomUUID();
|
||||||
|
}
|
||||||
|
return userId;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|||||||
@@ -35,12 +35,12 @@
|
|||||||
const data = JSON.parse(request.responseText);
|
const data = JSON.parse(request.responseText);
|
||||||
display(data);
|
display(data);
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to get "+ swaggerUrl)
|
alert("Failed to get " + swaggerUrl)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
request.onerror = function () {
|
request.onerror = function () {
|
||||||
alert("Failed to get "+ swaggerUrl)
|
alert("Failed to get " + swaggerUrl)
|
||||||
};
|
};
|
||||||
|
|
||||||
request.send();
|
request.send();
|
||||||
@@ -67,4 +67,5 @@
|
|||||||
reqOas();
|
reqOas();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
<script src="analytics.js"></script>
|
||||||
</html>
|
</html>
|
||||||
@@ -149,7 +149,7 @@ function showResources(namespace, chart, revision) {
|
|||||||
const resBody = $("#nav-resources .body");
|
const resBody = $("#nav-resources .body");
|
||||||
const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"];
|
const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"];
|
||||||
resBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
|
resBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
|
||||||
let url = "/api/helm/releases/" + namespace + "/" + chart + "/resources"
|
let url = "/api/helm/releases/" + namespace + "/" + chart + "/resources?health=true"
|
||||||
$.getJSON(url).fail(function (xhr) {
|
$.getJSON(url).fail(function (xhr) {
|
||||||
reportError("Failed to get list of resources", xhr)
|
reportError("Failed to get list of resources", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
@@ -180,23 +180,32 @@ function showResources(namespace, chart, revision) {
|
|||||||
|
|
||||||
resBody.append(resBlock)
|
resBody.append(resBlock)
|
||||||
let ns = res.metadata.namespace ? res.metadata.namespace : namespace
|
let ns = res.metadata.namespace ? res.metadata.namespace : namespace
|
||||||
$.getJSON("/api/k8s/" + res.kind.toLowerCase() + "/get?name=" + res.metadata.name + "&namespace=" + ns).fail(function () {
|
for (let k = 0; k < res.status.conditions.length; k++) {
|
||||||
//reportError("Failed to get list of resources")
|
if (res.status.conditions[k].type !== "hdHealth") { // it's our custom condition type
|
||||||
}).done(function (data) {
|
continue
|
||||||
const badge = $("<span class='badge me-2 fw-normal'></span>").text(data.status.phase);
|
}
|
||||||
if (["Available", "Active", "Established", "Bound", "Ready"].includes(data.status.phase)) {
|
|
||||||
|
const cond = res.status.conditions[k]
|
||||||
|
|
||||||
|
const badge = $("<span class='badge me-2 fw-normal'></span>").text(cond.reason);
|
||||||
|
if (cond.status === "Unknown") {
|
||||||
|
badge.addClass("bg-secondary text-danger")
|
||||||
|
} else if (cond.status === "Healthy") {
|
||||||
badge.addClass("bg-success text-dark")
|
badge.addClass("bg-success text-dark")
|
||||||
} else if (["Exists"].includes(data.status.phase)) {
|
} else if (cond.status === "Progressing") {
|
||||||
badge.addClass("bg-success text-dark bg-opacity-50")
|
|
||||||
} else if (["Progressing"].includes(data.status.phase)) {
|
|
||||||
badge.addClass("bg-warning")
|
badge.addClass("bg-warning")
|
||||||
} else {
|
} else {
|
||||||
badge.addClass("bg-danger")
|
badge.addClass("bg-danger")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (["Exists"].includes(cond.reason)) {
|
||||||
|
badge.addClass("bg-opacity-50")
|
||||||
|
}
|
||||||
|
|
||||||
const statusBlock = resBlock.find(".res-status");
|
const statusBlock = resBlock.find(".res-status");
|
||||||
statusBlock.empty().append(badge).attr("title", data.status.phase)
|
statusBlock.empty().append(badge).attr("title", cond.reason)
|
||||||
const statusMessage = getStatusMessage(data.status)
|
const statusMessage = cond.message
|
||||||
resBlock.find(".res-statusmsg").html("<span class='text-muted small'>" + (statusMessage ? statusMessage : '') + "</span>")
|
resBlock.find(".res-statusmsg").html("<span class='text-muted small me-2'>" + (statusMessage ? statusMessage : '') + "</span>")
|
||||||
|
|
||||||
if (badge.text() !== "NotFound" && revision == $("#specRev").data("last-rev")) {
|
if (badge.text() !== "NotFound" && revision == $("#specRev").data("last-rev")) {
|
||||||
resBlock.find(".res-actions")
|
resBlock.find(".res-actions")
|
||||||
@@ -215,21 +224,15 @@ function showResources(namespace, chart, revision) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
if (badge.hasClass("bg-danger") || badge.hasClass("bg-warning")) {
|
||||||
|
resBlock.find(".res-statusmsg").append("<a href='" + KomodorCTALink + "' class='btn btn-primary btn-sm fw-normal fs-80' target='_blank'>Troubleshoot in Komodor <i class='bi-box-arrow-up-right'></i></a>")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStatusMessage(status) {
|
|
||||||
if (!status) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (status.conditions) {
|
|
||||||
return status.conditions[0].message || status.conditions[0].reason
|
|
||||||
}
|
|
||||||
return status.message || status.reason
|
|
||||||
}
|
|
||||||
|
|
||||||
function showDescribe(ns, kind, name, badge) {
|
function showDescribe(ns, kind, name, badge) {
|
||||||
$("#describeModal .offcanvas-header p").text(kind)
|
$("#describeModal .offcanvas-header p").text(kind)
|
||||||
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
|
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
|
||||||
|
|||||||
@@ -69,9 +69,15 @@
|
|||||||
</a></li>
|
</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn" href="https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash"><img
|
<div class="border-muted text-muted border rounded p-1 pe-2 me-3 d-flex">
|
||||||
src="static/komodor-logo.svg" alt="komodor.io"
|
<img alt="Komodor" src="https://raw.githubusercontent.com/komodorio/helm-charts/master/k8s-watcher.svg" class="me-2" style="width: 42px; height: 42px"/>
|
||||||
style="height: 1.2rem; vertical-align: text-bottom; filter: grayscale(00%);"></a>
|
<span class="text-nowrap">
|
||||||
|
<a href="https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&utm_source=helm-dash&utm_medium=cta&utm_content=helm-dash"
|
||||||
|
class="link text-primary fw-bold text-decoration-none" target="_blank">Upgrade your HELM experience - Free
|
||||||
|
<i class="bi-box-arrow-up-right ms-1"></i></a><br/>
|
||||||
|
Auth & RBAC, k8s events, troubleshooting and more
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="separator-vertical"><span></span></div>
|
<div class="separator-vertical"><span></span></div>
|
||||||
<i class="btn bi-power text-muted p-2 m-1 mx-2" title="Shut down the Helm Dashboard application"></i>
|
<i class="btn bi-power text-muted p-2 m-1 mx-2" title="Shut down the Helm Dashboard application"></i>
|
||||||
@@ -89,6 +95,9 @@
|
|||||||
<button class="btn btn-sm border-secondary text-muted">
|
<button class="btn btn-sm border-secondary text-muted">
|
||||||
<i class="bi-plus-lg"></i> Add Repository
|
<i class="bi-plus-lg"></i> Add Repository
|
||||||
</button>
|
</button>
|
||||||
|
<div class="mt-2 p-2 small">Charts developers: you can also add local directories as chart source. Use
|
||||||
|
<span class="font-monospace text-success">--local-chart</span> CLI switch to specify it.
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
|
<div class="col-9 repo-details bg-white b-shadow pt-4 px-5 overflow-auto rounded">
|
||||||
@@ -153,14 +162,6 @@
|
|||||||
placeholder="Filter..."/>
|
placeholder="Filter..."/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bg-secondary rounded-bottom m-0 row p-2">
|
|
||||||
<div class="col-4 hdr-name">Name</div>
|
|
||||||
<div class="col-3">Release Status</div>
|
|
||||||
<div class="col-2">Chart</div>
|
|
||||||
<div class="col-1">Revision</div>
|
|
||||||
<div class="col-1">Namespace</div>
|
|
||||||
<div class="col-1">Updated</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="body"></div>
|
<div class="body"></div>
|
||||||
@@ -304,6 +305,8 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<p style="white-space: pre-wrap"></p>
|
<p style="white-space: pre-wrap"></p>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||||
|
<hr/>
|
||||||
|
<span class="small text-muted fs-80">Hint: Komodor has the same HELM capabilities, with enterprise features and support. <a href="https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&utm_source=helm-dash&utm_medium=cta&utm_content=helm-dash" target="_blank">Sign up for free.</a></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="describeModal"
|
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="describeModal"
|
||||||
@@ -313,8 +316,12 @@
|
|||||||
<h5 id="describeModalLabel"></h5>
|
<h5 id="describeModalLabel"></h5>
|
||||||
<p class="m-0 mt-4">ResourceType</p>
|
<p class="m-0 mt-4">ResourceType</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
<div>
|
||||||
|
<a href='https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&utm_source=helm-dash&utm_medium=cta&utm_content=helm-dash'
|
||||||
|
class='btn btn-primary btn-sm me-2' target='_blank'>See more details in Komodor <i
|
||||||
|
class='bi-box-arrow-up-right'></i></a>
|
||||||
|
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body p-2 ps-4" id="describeModalBody">
|
<div class="offcanvas-body p-2 ps-4" id="describeModalBody">
|
||||||
</div>
|
</div>
|
||||||
@@ -347,8 +354,26 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form enctype="application/x-www-form-urlencoded">
|
<form enctype="application/x-www-form-urlencoded">
|
||||||
<label class="form-label">Name: <input class="form-control" name="name"></label>
|
<div class="row mb-4">
|
||||||
<label class="form-label">URL: <input class="form-control" name="url"></label>
|
<div class="col">
|
||||||
|
<label class="form-label required">Name</label>
|
||||||
|
<input class="form-control" type="text" name="name" placeholder="Komodorio">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label required">URL</label>
|
||||||
|
<input class="form-control" type="text" name="url" placeholder="https://helm-charts.komodor.io">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Username</label>
|
||||||
|
<input class="form-control" type="text" name="username">
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<label class="form-label">Password</label>
|
||||||
|
<input class="form-control" type="password" name="password">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@@ -478,47 +503,5 @@
|
|||||||
<script src="static/actions.js"></script>
|
<script src="static/actions.js"></script>
|
||||||
<script src="static/scripts.js"></script>
|
<script src="static/scripts.js"></script>
|
||||||
|
|
||||||
<!-- BANNER START
|
|
||||||
<a id="banner"
|
|
||||||
href="https://helm-dashboard-survey.komodor.com/"
|
|
||||||
class="display-none position-absolute top-0 start-50 translate-middle-x bg-primary text-light rounded px-2 mt-1 text-decoration-none py-1">Help
|
|
||||||
shaping the future by participating in user survey <b class="bi-x-lg"></b></a>
|
|
||||||
<script>
|
|
||||||
function setCookie(name, value, days) {
|
|
||||||
let expires = "";
|
|
||||||
if (days) {
|
|
||||||
const date = new Date();
|
|
||||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
||||||
expires = "; expires=" + date.toUTCString();
|
|
||||||
}
|
|
||||||
document.cookie = name + "=" + (value || "") + expires + "; path=/";
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
const nameEQ = name + "=";
|
|
||||||
const ca = document.cookie.split(';');
|
|
||||||
for (let i = 0; i < ca.length; i++) {
|
|
||||||
const c = ca[i].trim();
|
|
||||||
if (c.indexOf(nameEQ) === 0) {
|
|
||||||
return c.substring(nameEQ.length, c.length)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cookie = getCookie("hideBanner");
|
|
||||||
if (cookie == null) {
|
|
||||||
console.log("show")
|
|
||||||
$("#banner").show()
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#banner b").click(function (evt) {
|
|
||||||
evt.preventDefault()
|
|
||||||
setCookie("hideBanner", "1", 365);
|
|
||||||
$("#banner").hide()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
/BANNER END -->
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -88,11 +88,54 @@ function buildChartCard(elm) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNewerVersion(elm.chartVersion, data[0].version)) {
|
if (isNewerVersion(elm.chartVersion, data[0].version) || data[0].isSuggestedRepo) {
|
||||||
card.find(".rel-name span").append("<span class='bi-arrow-up-circle-fill ms-2 text-success' title='Upgrade available: "+data[0].version+"'></span>")
|
const icon = $("<br/><span class='fw-bold' data-bs-toggle='tooltip' data-bs-placement='bottom'></span>")
|
||||||
|
if (data[0].isSuggestedRepo) {
|
||||||
|
icon.addClass("bi-plus-circle-fill text-primary")
|
||||||
|
icon.text(" ADD REPO")
|
||||||
|
icon.attr("data-bs-title", "Add '" + data[0].repository + "' to list of known repositories")
|
||||||
|
} else {
|
||||||
|
icon.addClass("bi-arrow-up-circle-fill text-primary")
|
||||||
|
icon.text(" UPGRADE")
|
||||||
|
icon.attr("data-bs-title", "Upgrade available: " + data[0].version + " from " + data[0].repository)
|
||||||
|
}
|
||||||
|
card.find(".rel-chart div").append(icon)
|
||||||
|
|
||||||
|
const tooltipTriggerList = card.find('.rel-chart [data-bs-toggle="tooltip"]')
|
||||||
|
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||||
|
sendStats('upgradeIconShown', {'isProbable': data[0].isSuggestedRepo})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// check resource health status
|
||||||
|
$.getJSON("/api/helm/releases/" + elm.namespace + "/" + elm.name + "/resources?health=true").fail(function (xhr) {
|
||||||
|
reportError("Failed to find chart in repo", xhr)
|
||||||
|
}).done(function (data) {
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
const res = data[i]
|
||||||
|
for (let k = 0; k < res.status.conditions.length; k++) {
|
||||||
|
if (res.status.conditions[k].type !== "hdHealth") { // it's our custom condition type
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const cond = res.status.conditions[k]
|
||||||
|
const square=$("<span class='me-1 mb-1 square rounded rounded-1' data-bs-toggle='tooltip'> </span>")
|
||||||
|
if (cond.status === "Healthy") {
|
||||||
|
square.addClass("bg-success")
|
||||||
|
} else if (cond.status === "Progressing") {
|
||||||
|
square.addClass("bg-warning")
|
||||||
|
} else {
|
||||||
|
square.addClass("bg-danger")
|
||||||
|
}
|
||||||
|
square.attr("data-bs-title", cond.status+" "+res.kind+" '"+res.metadata.name+"'")
|
||||||
|
card.find(".rel-status div").append(square)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tooltipTriggerList = card.find('.rel-status [data-bs-toggle="tooltip"]')
|
||||||
|
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
|
||||||
|
})
|
||||||
|
|
||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -302,6 +302,11 @@
|
|||||||
"name": "name",
|
"name": "name",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"description": "Name of Helm release"
|
"description": "Name of Helm release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "health",
|
||||||
|
"in": "query",
|
||||||
|
"description": "Flag to query k8s health status of resources"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"get": {
|
"get": {
|
||||||
|
|||||||
@@ -2,6 +2,13 @@ function loadRepoView() {
|
|||||||
$("#sectionRepo .repo-details").hide()
|
$("#sectionRepo .repo-details").hide()
|
||||||
$("#sectionRepo").show()
|
$("#sectionRepo").show()
|
||||||
|
|
||||||
|
$("#repoAddModal input[name=name]").val(getHashParam("suggestRepo"))
|
||||||
|
$("#repoAddModal input[name=url]").val(getHashParam("suggestRepoUrl"))
|
||||||
|
|
||||||
|
if (getHashParam("suggestRepo")) {
|
||||||
|
$("#sectionRepo .repo-list .btn").click()
|
||||||
|
}
|
||||||
|
|
||||||
$.getJSON("/api/helm/repositories").fail(function (xhr) {
|
$.getJSON("/api/helm/repositories").fail(function (xhr) {
|
||||||
reportError("Failed to get list of repositories", xhr)
|
reportError("Failed to get list of repositories", xhr)
|
||||||
sendStats('Get repo', {'status': 'fail'});
|
sendStats('Get repo', {'status': 'fail'});
|
||||||
@@ -85,6 +92,8 @@ $("#inputSearch").keyup(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
$("#sectionRepo .repo-list .btn").click(function () {
|
$("#sectionRepo .repo-list .btn").click(function () {
|
||||||
|
setHashParam("suggestRepo", null)
|
||||||
|
setHashParam("suggestRepoUrl", null)
|
||||||
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('repoAddModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -117,8 +117,8 @@ $("#topNav ul a").click(function () {
|
|||||||
initView()
|
initView()
|
||||||
})
|
})
|
||||||
|
|
||||||
const myAlert = document.getElementById('errorAlert')
|
const errAlert = document.getElementById('errorAlert')
|
||||||
myAlert.addEventListener('close.bs.alert', event => {
|
errAlert.addEventListener('close.bs.alert', event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
$("#errorAlert").hide()
|
$("#errorAlert").hide()
|
||||||
})
|
})
|
||||||
@@ -129,6 +129,7 @@ function reportError(err, xhr) {
|
|||||||
$("#errorAlert p").text(xhr.responseText)
|
$("#errorAlert p").text(xhr.responseText)
|
||||||
}
|
}
|
||||||
$("#errorAlert").show()
|
$("#errorAlert").show()
|
||||||
|
sendStats("errorReported", {"errMessage": err})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -357,3 +358,5 @@ function setFilteredNamespaces(filteredNamespaces) {
|
|||||||
setHashParam("filteredNamespace", filteredNamespaces.join('+'))
|
setHashParam("filteredNamespace", filteredNamespaces.join('+'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const KomodorCTALink="https://www.komodor.com/helm-dash/?utm_campaign=Helm%20Dashboard%20%7C%20CTA&utm_source=helm-dash&utm_medium=cta&utm_content=helm-dash"
|
||||||
@@ -79,3 +79,8 @@
|
|||||||
.fs-80 {
|
.fs-80 {
|
||||||
font-size: 0.8rem!important;
|
font-size: 0.8rem!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.required::after {
|
||||||
|
content: " *";
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@@ -301,3 +301,26 @@ nav .nav-tabs .nav-link.active {
|
|||||||
.test-result {
|
.test-result {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.square {
|
||||||
|
width: 0.55rem;
|
||||||
|
height: 0.55rem;
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 0.1rem!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square.bg-danger {
|
||||||
|
background-color: #ff0072!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square.bg-warning {
|
||||||
|
background-color: #ffa800!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.square.bg-success {
|
||||||
|
background-color: #00c2ab!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hljs {
|
||||||
|
overflow-x: inherit;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "dashboard"
|
name: "dashboard"
|
||||||
version: "1.1.0"
|
version: "1.3.0"
|
||||||
usage: "A simplified way of working with Helm"
|
usage: "A simplified way of working with Helm"
|
||||||
description: "View HELM situation in nice web UI"
|
description: "View HELM situation in nice web UI"
|
||||||
command: "$HELM_PLUGIN_DIR/bin/helm-dashboard"
|
command: "$HELM_PLUGIN_DIR/bin/helm-dashboard"
|
||||||
|
|||||||
Reference in New Issue
Block a user