mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-26 14:28:04 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cae4b5adf | ||
|
|
d8afa3861d | ||
|
|
3c4d73665e | ||
|
|
8b5f8e1031 | ||
|
|
44461bf5ab | ||
|
|
061bd12f2f | ||
|
|
86c9f89acc | ||
|
|
890994d70d | ||
|
|
35097fed45 | ||
|
|
11912e7b51 | ||
|
|
8e90c9f8d0 | ||
|
|
1e3a706698 |
@@ -8,7 +8,7 @@ A simplified way of working with Helm.
|
|||||||
|
|
||||||
The _Helm Dashboard_ plugin offers a UI-driven way to view the installed Helm charts, see their revision history and corresponding k8s resources. Also, you can perform simple actions like roll back to a revision or upgrade to newer version.
|
The _Helm Dashboard_ plugin offers a UI-driven way to view the installed Helm charts, see their revision history and corresponding k8s resources. Also, you can perform simple actions like roll back to a revision or upgrade to newer version.
|
||||||
|
|
||||||
This project is part of [Komodor's](https://komodor.io) vision of helping Kubernetes users to navigate and troubleshoot their clusters.
|
This project is part of [Komodor's](https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash-gh) vision of helping Kubernetes users to navigate and troubleshoot their clusters.
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ If you want to increase the logging verbosity and see all the debug info, set `D
|
|||||||
|
|
||||||
## Support Channels
|
## Support Channels
|
||||||
|
|
||||||
We have two main channels for supporting the Helm Dashboard users: [Slack community](https://komodorkommunity.slack.com/x-p3820586794880-3937175868755-4092688791734/archives/C042U85BD45/p1663573506220839) for general conversations
|
We have two main channels for supporting the Helm Dashboard users: [Slack community](https://komodorkommunity.slack.com/archives/C044U1B0265) for general conversations
|
||||||
and [GitHub issues](https://github.com/komodorio/helm-dashboard/issues) for real bugs.
|
and [GitHub issues](https://github.com/komodorio/helm-dashboard/issues) for real bugs.
|
||||||
|
|
||||||
|
|
||||||
@@ -76,10 +76,8 @@ and [GitHub issues](https://github.com/komodorio/helm-dashboard/issues) for real
|
|||||||
|
|
||||||
### Further Ideas
|
### Further Ideas
|
||||||
- solve umbrella-chart case
|
- solve umbrella-chart case
|
||||||
- use `--dry-run` instead of `template`
|
|
||||||
- Have cleaner idea on the web API structure
|
- Have cleaner idea on the web API structure
|
||||||
- Recognise & show ArgoCD-originating charts/objects, those `helm ls` does not show
|
- Recognise & show ArgoCD-originating charts/objects, those `helm ls` does not show
|
||||||
- Recognise the revisions that are rollbacks by their description and mark in timeline
|
|
||||||
|
|
||||||
#### Topic "Validating Manifests"
|
#### Topic "Validating Manifests"
|
||||||
|
|
||||||
|
|||||||
2
main.go
2
main.go
@@ -22,7 +22,7 @@ func main() {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
address, webServerDone := dashboard.StartServer()
|
address, webServerDone := dashboard.StartServer(version)
|
||||||
|
|
||||||
if os.Getenv("HD_NOBROWSER") == "" {
|
if os.Getenv("HD_NOBROWSER") == "" {
|
||||||
log.Infof("Opening web UI: %s", address)
|
log.Infof("Opening web UI: %s", address)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func errorHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(abortWeb ControlChan, data *DataLayer) *gin.Engine {
|
func NewRouter(abortWeb ControlChan, data *DataLayer, version string) *gin.Engine {
|
||||||
var api *gin.Engine
|
var api *gin.Engine
|
||||||
if os.Getenv("DEBUG") == "" {
|
if os.Getenv("DEBUG") == "" {
|
||||||
api = gin.New()
|
api = gin.New()
|
||||||
@@ -47,15 +47,20 @@ func NewRouter(abortWeb ControlChan, data *DataLayer) *gin.Engine {
|
|||||||
api.Use(errorHandler)
|
api.Use(errorHandler)
|
||||||
|
|
||||||
configureStatic(api)
|
configureStatic(api)
|
||||||
configureRoutes(abortWeb, data, api)
|
configureRoutes(abortWeb, data, api, version)
|
||||||
|
|
||||||
return api
|
return api
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureRoutes(abortWeb ControlChan, data *DataLayer, api *gin.Engine) {
|
func configureRoutes(abortWeb ControlChan, data *DataLayer, api *gin.Engine, version string) {
|
||||||
// server shutdown handler
|
// server shutdown handler
|
||||||
api.DELETE("/", func(c *gin.Context) {
|
api.DELETE("/", func(c *gin.Context) {
|
||||||
abortWeb <- struct{}{}
|
abortWeb <- struct{}{}
|
||||||
|
c.Status(http.StatusAccepted)
|
||||||
|
})
|
||||||
|
|
||||||
|
api.GET("/status", func(c *gin.Context) {
|
||||||
|
c.String(http.StatusOK, version)
|
||||||
})
|
})
|
||||||
|
|
||||||
configureHelms(api.Group("/api/helm"), data)
|
configureHelms(api.Group("/api/helm"), data)
|
||||||
@@ -71,7 +76,7 @@ func configureHelms(api *gin.RouterGroup, data *DataLayer) {
|
|||||||
api.GET("/charts/resources", h.Resources)
|
api.GET("/charts/resources", h.Resources)
|
||||||
api.GET("/repo/search", h.RepoSearch)
|
api.GET("/repo/search", h.RepoSearch)
|
||||||
api.POST("/repo/update", h.RepoUpdate)
|
api.POST("/repo/update", h.RepoUpdate)
|
||||||
api.GET("/charts/install", h.InstallPreview)
|
api.GET("/repo/values", h.RepoValues)
|
||||||
api.POST("/charts/install", h.Install)
|
api.POST("/charts/install", h.Install)
|
||||||
api.GET("/charts/:section", h.GetInfoSection)
|
api.GET("/charts/:section", h.GetInfoSection)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/hexops/gotextdiff/span"
|
"github.com/hexops/gotextdiff/span"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"io/ioutil"
|
"helm.sh/helm/v3/pkg/release"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -21,6 +21,17 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type CmdError struct {
|
||||||
|
Command []string
|
||||||
|
OrigError error
|
||||||
|
StdErr []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e CmdError) Error() string {
|
||||||
|
//return fmt.Sprintf("failed to run command %s:\nError: %s\nSTDERR:%s", e.Command, e.OrigError, e.StdErr)
|
||||||
|
return string(e.StdErr)
|
||||||
|
}
|
||||||
|
|
||||||
type DataLayer struct {
|
type DataLayer struct {
|
||||||
KubeContext string
|
KubeContext string
|
||||||
Helm string
|
Helm string
|
||||||
@@ -46,9 +57,18 @@ func (d *DataLayer) runCommand(cmd ...string) (string, error) {
|
|||||||
log.Warnf("STDERR:\n%s", serr)
|
log.Warnf("STDERR:\n%s", serr)
|
||||||
}
|
}
|
||||||
if eerr, ok := err.(*exec.ExitError); ok {
|
if eerr, ok := err.(*exec.ExitError); ok {
|
||||||
return "", fmt.Errorf("failed to run command %s:\nError: %s\nSTDERR:%s", cmd, eerr, serr)
|
return "", CmdError{
|
||||||
|
Command: cmd,
|
||||||
|
StdErr: serr,
|
||||||
|
OrigError: eerr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", CmdError{
|
||||||
|
Command: cmd,
|
||||||
|
StdErr: serr,
|
||||||
|
OrigError: err,
|
||||||
}
|
}
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sout := stdout.Bytes()
|
sout := stdout.Bytes()
|
||||||
@@ -96,12 +116,10 @@ func (d *DataLayer) CheckConnectivity() error {
|
|||||||
return errors.New("did not find any kubectl contexts configured")
|
return errors.New("did not find any kubectl contexts configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
_, err = d.runCommandHelm("--help") // no point in doing is, since the default context may be invalid
|
||||||
_, err = d.runCommandHelm("env") // no point in doing is, since the default context may be invalid
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -215,7 +233,7 @@ func (d *DataLayer) RevisionManifests(namespace string, chartName string, revisi
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string, revision int) ([]*GenericResource, error) {
|
func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string, revision int) ([]*v1.Carp, error) {
|
||||||
out, err := d.RevisionManifests(namespace, chartName, revision, false)
|
out, err := d.RevisionManifests(namespace, chartName, revision, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -223,7 +241,7 @@ func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string,
|
|||||||
|
|
||||||
dec := yaml.NewDecoder(bytes.NewReader([]byte(out)))
|
dec := yaml.NewDecoder(bytes.NewReader([]byte(out)))
|
||||||
|
|
||||||
res := make([]*GenericResource, 0)
|
res := make([]*v1.Carp, 0)
|
||||||
var tmp interface{}
|
var tmp interface{}
|
||||||
for dec.Decode(&tmp) == nil {
|
for dec.Decode(&tmp) == nil {
|
||||||
// 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
|
||||||
@@ -233,7 +251,7 @@ func (d *DataLayer) RevisionManifestsParsed(namespace string, chartName string,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var doc GenericResource
|
var doc v1.Carp
|
||||||
err = json.Unmarshal(jsoned, &doc)
|
err = json.Unmarshal(jsoned, &doc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -270,11 +288,11 @@ func (d *DataLayer) RevisionValues(namespace string, chartName string, revision
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataLayer) GetResource(namespace string, def *GenericResource) (*GenericResource, error) {
|
func (d *DataLayer) GetResource(namespace string, def *v1.Carp) (*v1.Carp, error) {
|
||||||
out, err := d.runCommandKubectl("get", strings.ToLower(def.Kind), def.Name, "--namespace", namespace, "--output", "json")
|
out, err := d.runCommandKubectl("get", strings.ToLower(def.Kind), def.Name, "--namespace", namespace, "--output", "json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.HasSuffix(strings.TrimSpace(err.Error()), " not found") {
|
if strings.HasSuffix(strings.TrimSpace(err.Error()), " not found") {
|
||||||
return &GenericResource{
|
return &v1.Carp{
|
||||||
Status: v1.CarpStatus{
|
Status: v1.CarpStatus{
|
||||||
Phase: "NotFound",
|
Phase: "NotFound",
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
@@ -286,7 +304,7 @@ func (d *DataLayer) GetResource(namespace string, def *GenericResource) (*Generi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var res GenericResource
|
var res v1.Carp
|
||||||
err = json.Unmarshal([]byte(out), &res)
|
err = json.Unmarshal([]byte(out), &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -348,29 +366,24 @@ func (d *DataLayer) ChartRepoUpdate(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DataLayer) ChartUpgrade(namespace string, name string, repoChart string, version string, justTemplate bool) (string, error) {
|
func (d *DataLayer) ChartUpgrade(namespace string, name string, repoChart string, version string, justTemplate bool, values string) (string, error) {
|
||||||
oldVals, err := d.RevisionValues(namespace, name, 0, false)
|
if values == "" {
|
||||||
|
oldVals, err := d.RevisionValues(namespace, name, 0, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
values = oldVals
|
||||||
|
}
|
||||||
|
|
||||||
|
oldValsFile, close1, err := tempFile(values)
|
||||||
|
defer close1()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := ioutil.TempFile("", "helm_vals_")
|
cmd := []string{"upgrade", name, repoChart, "--version", version, "--namespace", namespace, "--values", oldValsFile, "--output", "json"}
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer os.Remove(file.Name())
|
|
||||||
|
|
||||||
err = ioutil.WriteFile(file.Name(), []byte(oldVals), 0600)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := []string{name, repoChart, "--version", version, "--namespace", namespace, "--values", file.Name()}
|
|
||||||
if justTemplate {
|
if justTemplate {
|
||||||
cmd = append([]string{"template", "--skip-tests"}, cmd...)
|
cmd = append(cmd, "--dry-run")
|
||||||
} else {
|
|
||||||
cmd = append([]string{"upgrade"}, cmd...)
|
|
||||||
cmd = append(cmd, "--output", "json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
out, err := d.runCommandHelm(cmd...)
|
out, err := d.runCommandHelm(cmd...)
|
||||||
@@ -379,16 +392,33 @@ func (d *DataLayer) ChartUpgrade(namespace string, name string, repoChart string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if justTemplate {
|
if justTemplate {
|
||||||
|
res := release.Release{}
|
||||||
|
err = json.Unmarshal([]byte(out), &res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
manifests, err := d.RevisionManifests(namespace, name, 0, false)
|
manifests, err := d.RevisionManifests(namespace, name, 0, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
out = getDiff(strings.TrimSpace(manifests), strings.TrimSpace(out), "current.yaml", "upgraded.yaml")
|
out = getDiff(strings.TrimSpace(manifests), strings.TrimSpace(res.Manifest), "current.yaml", "upgraded.yaml")
|
||||||
|
} else {
|
||||||
|
res := release.Release{}
|
||||||
|
err = json.Unmarshal([]byte(out), &res)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
_ = res
|
||||||
}
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DataLayer) ShowValues(chart string, ver string) (string, error) {
|
||||||
|
return d.runCommandHelm("show", "values", chart, "--version", ver)
|
||||||
|
}
|
||||||
|
|
||||||
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
|
func RevisionDiff(functor SectionFn, ext string, namespace string, name string, revision1 int, revision2 int, flag bool) (string, error) {
|
||||||
if revision1 == 0 || revision2 == 0 {
|
if revision1 == 0 || revision2 == 0 {
|
||||||
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
|
log.Debugf("One of revisions is zero: %d %d", revision1, revision2)
|
||||||
@@ -416,5 +446,3 @@ func getDiff(text1 string, text2 string, name1 string, name2 string) string {
|
|||||||
log.Debugf("The diff is: %s", diff)
|
log.Debugf("The diff is: %s", diff)
|
||||||
return diff
|
return diff
|
||||||
}
|
}
|
||||||
|
|
||||||
type GenericResource = v1.Carp
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dashboard
|
|||||||
import (
|
import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"helm.sh/helm/v3/pkg/release"
|
"helm.sh/helm/v3/pkg/release"
|
||||||
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -63,7 +64,7 @@ func TestFlow(t *testing.T) {
|
|||||||
_ = manifests
|
_ = manifests
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
res := make([]*GenericResource, 0)
|
res := make([]*v1.Carp, 0)
|
||||||
for _, m := range manifests {
|
for _, m := range manifests {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
mc := m // fix the clojure
|
mc := m // fix the clojure
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
package dashboard
|
package dashboard
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"helm.sh/helm/v3/pkg/release"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@@ -113,30 +111,25 @@ func (h *HelmHandler) RepoUpdate(c *gin.Context) {
|
|||||||
c.Status(http.StatusNoContent)
|
c.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HelmHandler) InstallPreview(c *gin.Context) {
|
|
||||||
out, err := chartInstall(c, h.Data, true)
|
|
||||||
if err != nil {
|
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.String(http.StatusOK, out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *HelmHandler) Install(c *gin.Context) {
|
func (h *HelmHandler) Install(c *gin.Context) {
|
||||||
out, err := chartInstall(c, h.Data, false)
|
qp, err := getQueryProps(c, false)
|
||||||
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
justTemplate := c.Query("flag") != "true"
|
||||||
|
out, err := h.Data.ChartUpgrade(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), justTemplate, c.PostForm("values"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res := release.Release{}
|
if !justTemplate {
|
||||||
err = json.Unmarshal([]byte(out), &res)
|
c.Header("Content-Type", "application/json")
|
||||||
if err != nil {
|
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.IndentedJSON(http.StatusAccepted, res)
|
c.String(http.StatusAccepted, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HelmHandler) GetInfoSection(c *gin.Context) {
|
func (h *HelmHandler) GetInfoSection(c *gin.Context) {
|
||||||
@@ -156,17 +149,13 @@ func (h *HelmHandler) GetInfoSection(c *gin.Context) {
|
|||||||
c.String(http.StatusOK, res)
|
c.String(http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
func chartInstall(c *gin.Context, data *DataLayer, justTemplate bool) (string, error) {
|
func (h *HelmHandler) RepoValues(c *gin.Context) {
|
||||||
qp, err := getQueryProps(c, false)
|
out, err := h.Data.ShowValues(c.Query("chart"), c.Query("version"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
c.String(http.StatusOK, out)
|
||||||
out, err := data.ChartUpgrade(qp.Namespace, qp.Name, c.Query("chart"), c.Query("version"), justTemplate)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleGetSection(data *DataLayer, section string, rDiff string, qp *QueryProps, flag bool) (string, error) {
|
func handleGetSection(data *DataLayer, section string, rDiff string, qp *QueryProps, flag bool) (string, error) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (h *KubeHandler) GetResourceInfo(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := h.Data.GetResource(qp.Namespace, &GenericResource{
|
res, err := h.Data.GetResource(qp.Namespace, &v12.Carp{
|
||||||
TypeMeta: v1.TypeMeta{Kind: c.Param("kind")},
|
TypeMeta: v1.TypeMeta{Kind: c.Param("kind")},
|
||||||
ObjectMeta: v1.ObjectMeta{Name: qp.Name},
|
ObjectMeta: v1.ObjectMeta{Name: qp.Name},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartServer() (string, ControlChan) {
|
func StartServer(version string) (string, ControlChan) {
|
||||||
data := DataLayer{}
|
data := DataLayer{}
|
||||||
err := data.CheckConnectivity()
|
err := data.CheckConnectivity()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -28,7 +28,7 @@ func StartServer() (string, ControlChan) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abort := make(ControlChan)
|
abort := make(ControlChan)
|
||||||
api := NewRouter(abort, &data)
|
api := NewRouter(abort, &data, version)
|
||||||
done := startBackgroundServer(address, api, abort)
|
done := startBackgroundServer(address, api, abort)
|
||||||
|
|
||||||
return "http://" + address, done
|
return "http://" + address, done
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 20.5C16.2467 20.5 20.5 16.2467 20.5 11C20.5 5.75329 16.2467 1.5 11 1.5C5.75329 1.5 1.5 5.75329 1.5 11C1.5 16.2467 5.75329 20.5 11 20.5ZM11 22C17.0751 22 22 17.0751 22 11C22 4.92487 17.0751 0 11 0C4.92487 0 0 4.92487 0 11C0 17.0751 4.92487 22 11 22Z" fill="#3B3D45"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4718 16.3004C10.6247 16.8115 8.56283 16.3411 7.11084 14.8891C5.65885 13.4371 5.18842 11.3753 5.69955 9.52809L4.51969 8.34823C3.48499 10.8798 3.99515 13.8947 6.05018 15.9498C8.10522 18.0048 11.1201 18.515 13.6517 17.4803L12.4718 16.3004ZM10.213 5.55627C11.8698 5.31807 13.6144 5.83629 14.889 7.11092C16.1636 8.38555 16.6819 10.1302 16.4437 11.7869L17.6973 13.0406C18.4242 10.6477 17.8416 7.94219 15.9497 6.05026C14.0577 4.15833 11.3523 3.57578 8.95934 4.3026L10.213 5.55627Z" fill="#3B3D45"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 968 B |
@@ -4,6 +4,7 @@ $("#btnUpgradeCheck").click(function () {
|
|||||||
self.find(".spinner-border").show()
|
self.find(".spinner-border").show()
|
||||||
const repoName = self.data("repo")
|
const repoName = self.data("repo")
|
||||||
$("#btnUpgrade span").text("Checking...")
|
$("#btnUpgrade span").text("Checking...")
|
||||||
|
$("#btnUpgrade .icon").removeClass("bi-arrow-up bi-pencil").addClass("bi-hourglass")
|
||||||
$.post("/api/helm/repo/update?name=" + repoName).fail(function (xhr) {
|
$.post("/api/helm/repo/update?name=" + repoName).fail(function (xhr) {
|
||||||
reportError("Failed to update chart repo", xhr)
|
reportError("Failed to update chart repo", xhr)
|
||||||
}).done(function () {
|
}).done(function () {
|
||||||
@@ -20,26 +21,38 @@ function checkUpgradeable(name) {
|
|||||||
$.getJSON("/api/helm/repo/search?name=" + name).fail(function (xhr) {
|
$.getJSON("/api/helm/repo/search?name=" + name).fail(function (xhr) {
|
||||||
reportError("Failed to find chart in repo", xhr)
|
reportError("Failed to find chart in repo", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
if (!data) {
|
if (!data || !data.length) {
|
||||||
|
$("#btnUpgrade span").text("No upgrades")
|
||||||
|
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-x-octagon")
|
||||||
|
$("#btnUpgrade").prop("disabled", true)
|
||||||
|
$("#btnUpgradeCheck").prop("disabled", true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#upgradeModalLabel select').empty()
|
const verCur = $("#specRev").data("last-chart-ver");
|
||||||
|
$('#upgradeModal select').empty()
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
$('#upgradeModalLabel select').append("<option value='" + data[i].version + "'>" + data[i].version + "</option>")
|
const opt = $("<option value='" + data[i].version + "'></option>");
|
||||||
|
if (data[i].version === verCur) {
|
||||||
|
opt.html(data[i].version + " ·")
|
||||||
|
} else {
|
||||||
|
opt.html(data[i].version)
|
||||||
|
}
|
||||||
|
$('#upgradeModal select').append(opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
const elm = data[0]
|
const elm = data[0]
|
||||||
$("#btnUpgradeCheck").data("repo", elm.name.split('/').shift())
|
$("#btnUpgradeCheck").data("repo", elm.name.split('/').shift())
|
||||||
$("#btnUpgradeCheck").data("chart", elm.name.split('/').pop())
|
$("#btnUpgradeCheck").data("chart", elm.name.split('/').pop())
|
||||||
|
|
||||||
const verCur = $("#specRev").data("last-chart-ver");
|
|
||||||
const canUpgrade = isNewerVersion(verCur, elm.version);
|
const canUpgrade = isNewerVersion(verCur, elm.version);
|
||||||
$("#btnUpgradeCheck").prop("disabled", false)
|
$("#btnUpgradeCheck").prop("disabled", false)
|
||||||
if (canUpgrade) {
|
if (canUpgrade) {
|
||||||
$("#btnUpgrade span").text("Upgrade to " + elm.version)
|
$("#btnUpgrade span").text("Upgrade to " + elm.version)
|
||||||
|
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-arrow-up")
|
||||||
} else {
|
} else {
|
||||||
$("#btnUpgrade span").text("No upgrades")
|
$("#btnUpgrade span").text("Reconfigure")
|
||||||
|
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-pencil")
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#btnUpgrade").off("click").click(function () {
|
$("#btnUpgrade").off("click").click(function () {
|
||||||
@@ -51,41 +64,100 @@ function checkUpgradeable(name) {
|
|||||||
function popUpUpgrade(self, verCur, elm) {
|
function popUpUpgrade(self, verCur, elm) {
|
||||||
const name = getHashParam("chart");
|
const name = getHashParam("chart");
|
||||||
let url = "/api/helm/charts/install?namespace=" + getHashParam("namespace") + "&name=" + name + "&chart=" + elm.name;
|
let url = "/api/helm/charts/install?namespace=" + getHashParam("namespace") + "&name=" + name + "&chart=" + elm.name;
|
||||||
$('#upgradeModalLabel select').data("url", url)
|
$('#upgradeModal select').data("url", url).data("chart", elm.name)
|
||||||
|
|
||||||
$("#upgradeModalLabel .name").text(name)
|
$("#upgradeModalLabel .name").text(name)
|
||||||
$("#upgradeModalLabel .ver-old").text(verCur)
|
$("#upgradeModal .ver-old").text(verCur)
|
||||||
|
|
||||||
$('#upgradeModalLabel select').val(elm.version).trigger("change")
|
$('#upgradeModal select').val(elm.version).trigger("change")
|
||||||
|
|
||||||
const myModal = new bootstrap.Offcanvas(document.getElementById('upgradeModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('upgradeModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
|
|
||||||
const btnConfirm = $("#upgradeModal .btn-confirm");
|
const btnConfirm = $("#upgradeModal .btn-confirm");
|
||||||
btnConfirm.prop("disabled", true).off('click').click(function () {
|
btnConfirm.prop("disabled", true).off('click').click(function () {
|
||||||
console.log("working")
|
|
||||||
btnConfirm.prop("disabled", true).prepend('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
btnConfirm.prop("disabled", true).prepend('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url + "&version=" + $('#upgradeModalLabel select').val(),
|
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
|
url: url + "&version=" + $('#upgradeModal select').val() + "&flag=true",
|
||||||
|
data: $("#upgradeModal textarea").data("dirty") ? $("#upgradeModal form").serialize() : null,
|
||||||
}).fail(function (xhr) {
|
}).fail(function (xhr) {
|
||||||
reportError("Failed to upgrade the chart", xhr)
|
reportError("Failed to upgrade the chart", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
setHashParam("revision", data.version)
|
console.log(data)
|
||||||
window.location.reload()
|
if (data.version) {
|
||||||
|
setHashParam("revision", data.version)
|
||||||
|
window.location.reload()
|
||||||
|
} else {
|
||||||
|
reportError("Failed to get new revision number")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// fill current values
|
||||||
|
const lastRev = $("#specRev").data("last-rev")
|
||||||
|
$.get("/api/helm/charts/values?namespace=" + getHashParam("namespace") + "&revision=" + lastRev + "&name=" + getHashParam("chart") + "&flag=true").fail(function (xhr) {
|
||||||
|
reportError("Failed to get charts values info", xhr)
|
||||||
|
}).done(function (data) {
|
||||||
|
$("#upgradeModal textarea").val(data).data("dirty", false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$('#upgradeModalLabel select').change(function () {
|
let reconfigTimeout = null;
|
||||||
|
$("#upgradeModal textarea").keyup(function () {
|
||||||
|
const self = $(this);
|
||||||
|
self.data("dirty", true)
|
||||||
|
if (reconfigTimeout) {
|
||||||
|
window.clearTimeout(reconfigTimeout)
|
||||||
|
}
|
||||||
|
reconfigTimeout = window.setTimeout(function () {
|
||||||
|
requestChangeDiff()
|
||||||
|
}, 500)
|
||||||
|
})
|
||||||
|
|
||||||
|
$('#upgradeModal select').change(function () {
|
||||||
const self = $(this)
|
const self = $(this)
|
||||||
|
|
||||||
$("#upgradeModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
requestChangeDiff()
|
||||||
$("#upgradeModal .btn-confirm").prop("disabled", true)
|
|
||||||
$.get(self.data("url") + "&version=" + self.val()).fail(function (xhr) {
|
// fill reference values
|
||||||
|
$("#upgradeModal .ref-vals").html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
||||||
|
$.get("/api/helm/repo/values?chart=" + self.data("chart") + "&version=" + self.val()).fail(function (xhr) {
|
||||||
reportError("Failed to get upgrade info", xhr)
|
reportError("Failed to get upgrade info", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
$("#upgradeModalBody").empty();
|
data = hljs.highlight(data, {language: 'yaml'}).value
|
||||||
|
$("#upgradeModal .ref-vals").html(data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function requestChangeDiff() {
|
||||||
|
const self = $('#upgradeModal select');
|
||||||
|
const diffBody = $("#upgradeModalBody");
|
||||||
|
diffBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Calculating diff...')
|
||||||
|
$("#upgradeModal .btn-confirm").prop("disabled", true)
|
||||||
|
|
||||||
|
let values = null;
|
||||||
|
if ($("#upgradeModal textarea").data("dirty")) {
|
||||||
|
$("#upgradeModal .invalid-feedback").hide()
|
||||||
|
values = $("#upgradeModal form").serialize()
|
||||||
|
|
||||||
|
try {
|
||||||
|
jsyaml.load($("#upgradeModal textarea").val())
|
||||||
|
} catch (e) {
|
||||||
|
$("#upgradeModal .invalid-feedback").text("YAML parse error: "+e.message).show()
|
||||||
|
$("#upgradeModalBody").html("Invalid values YAML")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: self.data("url") + "&version=" + self.val(),
|
||||||
|
data: values,
|
||||||
|
}).fail(function (xhr) {
|
||||||
|
$("#upgradeModalBody").html("<p class='text-danger'>Failed to get upgrade info: "+ xhr.responseText+"</p>")
|
||||||
|
}).done(function (data) {
|
||||||
|
diffBody.empty();
|
||||||
$("#upgradeModal .btn-confirm").prop("disabled", false)
|
$("#upgradeModal .btn-confirm").prop("disabled", false)
|
||||||
|
|
||||||
const targetElement = document.getElementById('upgradeModalBody');
|
const targetElement = document.getElementById('upgradeModalBody');
|
||||||
@@ -95,12 +167,11 @@ $('#upgradeModalLabel select').change(function () {
|
|||||||
};
|
};
|
||||||
const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration);
|
const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration);
|
||||||
diff2htmlUi.draw()
|
diff2htmlUi.draw()
|
||||||
$("#upgradeModalBody").prepend("<p>Following changes will happen to cluster:</p>")
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
$("#upgradeModalBody").html("No changes will happen to cluster")
|
diffBody.html("No changes will happen to the cluster")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
|
||||||
const btnConfirm = $("#confirmModal .btn-confirm");
|
const btnConfirm = $("#confirmModal .btn-confirm");
|
||||||
$("#btnUninstall").click(function () {
|
$("#btnUninstall").click(function () {
|
||||||
@@ -122,7 +193,7 @@ $("#btnUninstall").click(function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const myModal = new bootstrap.Offcanvas(document.getElementById('confirmModal'));
|
const myModal = new bootstrap.Modal(document.getElementById('confirmModal'));
|
||||||
myModal.show()
|
myModal.show()
|
||||||
|
|
||||||
let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revision
|
let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revision
|
||||||
@@ -160,7 +231,7 @@ $("#btnRollback").click(function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const myModal = new bootstrap.Offcanvas(document.getElementById('confirmModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('confirmModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
|
|
||||||
let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revisionNew + "&revisionDiff=" + revisionCur
|
let qstr = "name=" + chart + "&namespace=" + namespace + "&revision=" + revisionNew + "&revisionDiff=" + revisionCur
|
||||||
|
|||||||
26
pkg/dashboard/static/datadog.js
Normal file
26
pkg/dashboard/static/datadog.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
(function(h,o,u,n,d) {
|
||||||
|
h=h[d]=h[d]||{q:[],onReady:function(c){h.q.push(c)}}
|
||||||
|
d=o.createElement(u);d.async=1;d.src=n
|
||||||
|
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n)
|
||||||
|
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-rum-v4.js','DD_RUM')
|
||||||
|
DD_RUM.onReady(function() {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
const version = xhr.responseText;
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE && version!=="dev") {
|
||||||
|
DD_RUM.init({
|
||||||
|
clientToken: 'pub16d64cd1c00cf073ce85af914333bf72',
|
||||||
|
applicationId: 'e75439e5-e1b3-46ba-a9e9-a2e58579a2e2',
|
||||||
|
site: 'datadoghq.com',
|
||||||
|
service: 'helm-dashboard',
|
||||||
|
version: version,
|
||||||
|
trackInteractions: true,
|
||||||
|
trackResources: true,
|
||||||
|
trackLongTasks: true,
|
||||||
|
defaultPrivacyLevel: 'mask'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.open('GET', '/status', true);
|
||||||
|
xhr.send(null);
|
||||||
|
})
|
||||||
@@ -5,10 +5,11 @@ function revisionClicked(namespace, name, self) {
|
|||||||
self.removeClass(inactive).addClass(active)
|
self.removeClass(inactive).addClass(active)
|
||||||
const elm = self.data("elm")
|
const elm = self.data("elm")
|
||||||
setHashParam("revision", elm.revision)
|
setHashParam("revision", elm.revision)
|
||||||
$("#sectionDetails span.rev").text("#"+elm.revision)
|
$("#sectionDetails span.rev").text("#" + elm.revision)
|
||||||
statusStyle(elm.status, $("#none"), $("#sectionDetails .rev-details .rev-status"))
|
statusStyle(elm.status, $("#none"), $("#sectionDetails .rev-details .rev-status"))
|
||||||
|
|
||||||
$("#sectionDetails .rev-date").text(elm.updated.replace("T", " ").replace("+", " +"))
|
const rdate = luxon.DateTime.fromISO(elm.updated);
|
||||||
|
$("#sectionDetails .rev-date").text(rdate.toJSDate().toLocaleString())
|
||||||
$("#sectionDetails .rev-tags .rev-chart").text(elm.chart)
|
$("#sectionDetails .rev-tags .rev-chart").text(elm.chart)
|
||||||
$("#sectionDetails .rev-tags .rev-app").text(elm.app_version)
|
$("#sectionDetails .rev-tags .rev-app").text(elm.app_version)
|
||||||
$("#sectionDetails .rev-tags .rev-ns").text(getHashParam("namespace"))
|
$("#sectionDetails .rev-tags .rev-ns").text(getHashParam("namespace"))
|
||||||
@@ -94,9 +95,11 @@ $('#specRev').keyup(function (event) {
|
|||||||
if (keycode == '13') {
|
if (keycode == '13') {
|
||||||
$("#diffModeRev").click()
|
$("#diffModeRev").click()
|
||||||
}
|
}
|
||||||
event.preventDefault()
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("form").submit(function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
$("#userDefinedVals").change(function () {
|
$("#userDefinedVals").change(function () {
|
||||||
const self = $(this)
|
const self = $(this)
|
||||||
@@ -150,10 +153,11 @@ function showResources(namespace, chart, revision) {
|
|||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
const res = data[i]
|
const res = data[i]
|
||||||
const resBlock = $(`
|
const resBlock = $(`
|
||||||
<div class="row px-3 py-2 mb-2">
|
<div class="row px-3 py-2 mb-3 bg-white rounded">
|
||||||
<div class="col-2 res-kind"></div>
|
<div class="col-2 res-kind text-break"></div>
|
||||||
<div class="col-4 res-name"></div>
|
<div class="col-3 res-name text-break fw-bold"></div>
|
||||||
<div class="col-5 res-status"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span class="text-muted small">Getting status...</span></div>
|
<div class="col-1 res-status"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></div>
|
||||||
|
<div class="col-5 res-statusmsg"><span class="text-muted small">Getting status...</span></div>
|
||||||
<div class="col-1 res-actions"></div>
|
<div class="col-1 res-actions"></div>
|
||||||
</div>
|
</div>
|
||||||
`)
|
`)
|
||||||
@@ -166,11 +170,11 @@ function showResources(namespace, chart, revision) {
|
|||||||
$.getJSON("/api/kube/resources/" + res.kind.toLowerCase() + "?name=" + res.metadata.name + "&namespace=" + ns).fail(function () {
|
$.getJSON("/api/kube/resources/" + res.kind.toLowerCase() + "?name=" + res.metadata.name + "&namespace=" + ns).fail(function () {
|
||||||
//reportError("Failed to get list of resources")
|
//reportError("Failed to get list of resources")
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
const badge = $("<span class='badge me-2'></span>").text(data.status.phase);
|
const badge = $("<span class='badge me-2 fw-normal'></span>").text(data.status.phase);
|
||||||
if (["Available", "Active", "Established"].includes(data.status.phase)) {
|
if (["Available", "Active", "Established"].includes(data.status.phase)) {
|
||||||
badge.addClass("bg-success")
|
badge.addClass("bg-success text-dark")
|
||||||
} else if (["Exists"].includes(data.status.phase)) {
|
} else if (["Exists"].includes(data.status.phase)) {
|
||||||
badge.addClass("bg-success bg-opacity-50")
|
badge.addClass("bg-success text-dark bg-opacity-50")
|
||||||
} else if (["Progressing"].includes(data.status.phase)) {
|
} else if (["Progressing"].includes(data.status.phase)) {
|
||||||
badge.addClass("bg-warning")
|
badge.addClass("bg-warning")
|
||||||
} else {
|
} else {
|
||||||
@@ -178,13 +182,15 @@ function showResources(namespace, chart, revision) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const statusBlock = resBlock.find(".res-status");
|
const statusBlock = resBlock.find(".res-status");
|
||||||
statusBlock.empty().append(badge).append("<span class='text-muted small'>" + (data.status.message ? data.status.message : '') + "</span>")
|
statusBlock.empty().append(badge)
|
||||||
|
resBlock.find(".res-statusmsg").html("<span class='text-muted small'>" + (data.status.message ? data.status.message : '') + "</span>")
|
||||||
|
|
||||||
if (badge.text() !== "NotFound") {
|
if (badge.text() !== "NotFound") {
|
||||||
resBlock.find(".res-actions")
|
resBlock.find(".res-actions")
|
||||||
resBlock.find(".res-actions").append("<i class=\"btn bi-zoom-in float-end text-muted\"></i>")
|
const btn = $("<button class=\"btn btn-sm btn-white border-secondary\">Describe</button>");
|
||||||
statusBlock.find(".bi-zoom-in").click(function () {
|
resBlock.find(".res-actions").append(btn)
|
||||||
showDescribe(ns, res.kind, res.metadata.name)
|
btn.click(function () {
|
||||||
|
showDescribe(ns, res.kind, res.metadata.name, badge.clone())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -192,8 +198,9 @@ function showResources(namespace, chart, revision) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function showDescribe(ns, kind, name) {
|
function showDescribe(ns, kind, name, badge) {
|
||||||
$("#describeModalLabel").text("Describe " + kind + ": " + ns + " / " + name)
|
$("#describeModal .offcanvas-header p").text(kind)
|
||||||
|
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
|
||||||
$("#describeModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
$("#describeModalBody").empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
|
||||||
|
|
||||||
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
|
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
|
||||||
|
|||||||
@@ -5,8 +5,9 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Helm Dashboard</title>
|
<title>Helm Dashboard</title>
|
||||||
|
<script src="static/datadog.js"></script>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://fonts.googleapis.com/css2?family=Roboto&family=Inter&family=Poppins:wght@600&family=Inter:wght@500&family=Roboto+Slab:wght@400&family=Roboto+Slab:wght@700&family=Roboto:wght@700"/>
|
href="https://fonts.googleapis.com/css2?family=Roboto&family=Inter&family=Poppins:wght@600&family=Poppins:wght@500&family=Inter:wght@500&family=Roboto+Slab:wght@400&family=Roboto+Slab:wght@700&family=Roboto:wght@700&family=Roboto:wght@500"/>
|
||||||
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
|
integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous">
|
||||||
@@ -14,6 +15,21 @@
|
|||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/lightfair.min.css"/>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.13.1/styles/lightfair.min.css"/>
|
||||||
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"/>
|
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css"/>
|
||||||
<link href="static/styles.css" rel="stylesheet">
|
<link href="static/styles.css" rel="stylesheet">
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.heap = window.heap || [], heap.load = function (e, t) {
|
||||||
|
window.heap.appid = e, window.heap.config = t = t || {};
|
||||||
|
var r = document.createElement("script");
|
||||||
|
r.type = "text/javascript", r.async = !0, r.src = "https://cdn.heapanalytics.com/js/heap-" + e + ".js";
|
||||||
|
var a = document.getElementsByTagName("script")[0];
|
||||||
|
a.parentNode.insertBefore(r, a);
|
||||||
|
for (var n = function (e) {
|
||||||
|
return function () {
|
||||||
|
heap.push([e].concat(Array.prototype.slice.call(arguments, 0)))
|
||||||
|
}
|
||||||
|
}, p = ["addEventProperties", "addUserProperties", "clearEventProperties", "identify", "resetIdentity", "removeEventProperty", "setEventProperties", "track", "unsetEventProperty"], o = 0; o < p.length; o++) heap[p[o]] = n(p[o])
|
||||||
|
};
|
||||||
|
heap.load("3615793373");
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@@ -25,8 +41,6 @@
|
|||||||
<a href="/"><img src="static/logo.png" alt="Logo"></a>
|
<a href="/"><img src="static/logo.png" alt="Logo"></a>
|
||||||
<div>
|
<div>
|
||||||
<h1><a href="/">Helm Dashboard</a></h1>
|
<h1><a href="/">Helm Dashboard</a></h1>
|
||||||
<p><span class="mr-1">by</span><a href="https://komodor.io"><img src="static/komodor-logo.svg"
|
|
||||||
alt="komodor.io"></a></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -47,6 +61,14 @@
|
|||||||
</li>
|
</li>
|
||||||
-->
|
-->
|
||||||
</ul>
|
</ul>
|
||||||
|
<div>
|
||||||
|
<a class="btn" href="https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash"><img
|
||||||
|
src="https://komodor.com/wp-content/uploads/2021/05/favicon.png" alt="komodor.io"
|
||||||
|
style="height: 1.2rem; vertical-align: text-bottom; filter: grayscale(00%);"></a>
|
||||||
|
|
||||||
|
<a class="btn me-2 text-muted" href="https://github.com/komodorio/helm-dashboard"
|
||||||
|
title="Project page on GitHub"><i class="bi-github"></i></a>
|
||||||
|
</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>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,21 +130,21 @@
|
|||||||
<h1 class="name float-start">Name</h1>
|
<h1 class="name float-start">Name</h1>
|
||||||
<div id="actionButtons" class="float-end">
|
<div id="actionButtons" class="float-end">
|
||||||
<span><button id="btnUpgrade"
|
<span><button id="btnUpgrade"
|
||||||
class="opacity-10 btn btn-sm btn-light bg-white rounded-0 me-0 rounded-start border-secondary">
|
class="opacity-10 btn btn-sm btn-light bg-white me-2 border-secondary">
|
||||||
<img src="static/action.svg"> <span>Checking...</span>
|
<i class="icon bi-hourglass-split"></i> <span></span>
|
||||||
</button></span><span><button
|
</button></span>
|
||||||
id="btnUpgradeCheck"
|
|
||||||
class="btn btn-sm text-muted btn-light bg-white border-secondary rounded-0 rounded-end ms-0 me-2"
|
|
||||||
title="Check for newer chart version from repo"><i class="bi-repeat"></i><span
|
|
||||||
class="spinner-border spinner-border-sm" style="display: none" role="status"
|
|
||||||
aria-hidden="true"></span></button></span>
|
|
||||||
|
|
||||||
<button id="btnRollback" class="btn btn-sm btn-light bg-white border border-secondary me-2"
|
<button id="btnRollback" class="btn btn-sm btn-light bg-white border border-secondary me-2"
|
||||||
title="Rollback to this revision"><img src="static/action.svg"> <span>Rollback</span>
|
title="Rollback to this revision"><i class="bi-arrow-repeat"></i> <span>Rollback</span>
|
||||||
</button>
|
</button>
|
||||||
<button id="btnUninstall" class="btn btn-sm btn-light bg-white border border-secondary"
|
<button id="btnUninstall" class="btn btn-sm btn-light bg-white border border-secondary"
|
||||||
title="Uninstall the chart"><img src="static/action.svg"> Uninstall
|
title="Uninstall the chart"><i class="bi-trash3"></i> Uninstall
|
||||||
</button>
|
</button>
|
||||||
|
<br/>
|
||||||
|
<a class="link small" id="btnUpgradeCheck">Check for new version
|
||||||
|
<span class="spinner-border spinner-border-sm" style="display: none" role="status"
|
||||||
|
aria-hidden="true"></span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="fs-2"> </div>
|
<div class="fs-2"> </div>
|
||||||
</div>
|
</div>
|
||||||
@@ -133,14 +155,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="rev-tags mt-3">
|
<div class="rev-tags mt-3">
|
||||||
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">cluster: <span
|
|
||||||
class="rev-cluster fw-bold"></span></span>
|
|
||||||
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">namespace: <span
|
|
||||||
class="rev-ns fw-bold"></span></span>
|
|
||||||
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">chart version: <span
|
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">chart version: <span
|
||||||
class="rev-chart fw-bold"></span></span>
|
class="rev-chart fw-bold"></span></span>
|
||||||
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">app version: <span
|
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">app version: <span
|
||||||
class="rev-app fw-bold"></span></span>
|
class="rev-app fw-bold"></span></span>
|
||||||
|
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">namespace: <span
|
||||||
|
class="rev-ns fw-bold"></span></span>
|
||||||
|
<span class="rounded rounded-1 me-2 p-1 px-2 bg-tag text-dark">cluster: <span
|
||||||
|
class="rev-cluster fw-bold"></span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="revDescr" class="mt-3 mb-4"></div>
|
<div id="revDescr" class="mt-3 mb-4"></div>
|
||||||
@@ -157,7 +179,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="values"
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="values"
|
||||||
type="button" role="tab" aria-controls="nav-disabled" aria-selected="false" tabindex="-1">
|
type="button" role="tab" aria-controls="nav-disabled" aria-selected="false" tabindex="-1">
|
||||||
Parameterized Values
|
Values
|
||||||
</button>
|
</button>
|
||||||
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="notes"
|
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#nav-manifest" data-tab="notes"
|
||||||
type="button" role="tab" aria-controls="nav-disabled" aria-selected="false" tabindex="-1">
|
type="button" role="tab" aria-controls="nav-disabled" aria-selected="false" tabindex="-1">
|
||||||
@@ -167,12 +189,13 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-pane p-3 container-fluid" id="nav-resources" role="tabpanel">
|
<div class="tab-pane p-3 container-fluid" id="nav-resources" role="tabpanel">
|
||||||
<div class="row bg-secondary rounded px-3 py-2 mb-2 fw-bold small"
|
<div class="row bg-secondary rounded px-3 py-2 mb-3 fw-bold small"
|
||||||
style="text-transform: uppercase">
|
style="text-transform: uppercase">
|
||||||
<div class="col-2">Kind</div>
|
<div class="col-2">Resource Type</div>
|
||||||
<div class="col-4">Name</div>
|
<div class="col-3">Name</div>
|
||||||
<div class="col-5">Status</div>
|
<div class="col-1">Status</div>
|
||||||
<div class="col-1">Describe</div>
|
<div class="col-5">Status Message</div>
|
||||||
|
<div class="col-1"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="body"></div>
|
<div class="body"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,7 +205,7 @@
|
|||||||
<label class="form-check-label" for="diffModeNone">
|
<label class="form-check-label" for="diffModeNone">
|
||||||
<input class="form-check-input" type="radio" name="diffMode" id="diffModeNone"
|
<input class="form-check-input" type="radio" name="diffMode" id="diffModeNone"
|
||||||
data-mode="view">
|
data-mode="view">
|
||||||
View Current
|
View
|
||||||
</label>
|
</label>
|
||||||
<label class="form-check-label" for="diffModePrev">
|
<label class="form-check-label" for="diffModePrev">
|
||||||
<input class="form-check-input" type="radio" name="diffMode" id="diffModePrev"
|
<input class="form-check-input" type="radio" name="diffMode" id="diffModePrev"
|
||||||
@@ -212,7 +235,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<!-- Modals -->
|
<!-- Modals -->
|
||||||
<div id="errorAlert"
|
<div id="errorAlert" style="z-index: 2000"
|
||||||
class="display-none alert alert-sm alert-danger alert-dismissible position-absolute position-absolute top-0 start-50 translate-middle-x mt-3 border-danger"
|
class="display-none alert alert-sm alert-danger alert-dismissible position-absolute position-absolute top-0 start-50 translate-middle-x mt-3 border-danger"
|
||||||
role="alert">
|
role="alert">
|
||||||
<h4 class="alert-heading"><i class="bi-exclamation-triangle-fill"></i> <span></span></h4>
|
<h4 class="alert-heading"><i class="bi-exclamation-triangle-fill"></i> <span></span></h4>
|
||||||
@@ -223,59 +246,83 @@
|
|||||||
|
|
||||||
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="describeModal"
|
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="describeModal"
|
||||||
aria-labelledby="describeModalLabel">
|
aria-labelledby="describeModalLabel">
|
||||||
<div class="offcanvas-header">
|
<div class="offcanvas-header border-bottom p-4">
|
||||||
<h5 id="describeModalLabel"></h5>
|
<div>
|
||||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
<h5 id="describeModalLabel"></h5>
|
||||||
</div>
|
<p class="m-0 mt-4">ResourceType</p>
|
||||||
<div class="offcanvas-body" id="describeModalBody">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="confirmModal" aria-labelledby="confirmModalLabel">
|
|
||||||
<div class="offcanvas-header">
|
|
||||||
<h5 id="confirmModalLabel"></h5>
|
|
||||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="offcanvas-body" id="confirmModalBody">
|
|
||||||
</div>
|
|
||||||
<div class="offcanvas-footer p-3">
|
|
||||||
<button type="button" class="btn btn-primary float-end btn-confirm">Confirm</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="offcanvas offcanvas-end rounded-start" tabindex="-1" id="upgradeModal" aria-labelledby="upgradeModalLabel">
|
|
||||||
<div class="offcanvas-header">
|
|
||||||
<h5 id="upgradeModalLabel">
|
|
||||||
Upgrade <b class='text-success name'></b> from version <b class='text-success ver-old'></b> to
|
|
||||||
<select class='fw-bold text-success ver-new'></select>
|
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
<div class="offcanvas-body" id="upgradeModalBody">
|
|
||||||
</div>
|
|
||||||
<div class="offcanvas-footer p-3">
|
|
||||||
<button type="button" class="btn btn-primary float-end btn-confirm">Confirm Upgrade</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<footer class="container-fluid small mt-5 mb-5" style="z-index: -50">
|
|
||||||
<div class="row align-items-end justify-content-end">
|
|
||||||
<div class="col-3"></div>
|
|
||||||
<div class="col-4 text-center bg-white bg-opacity-50 p-2 px-3 rounded b-shadow">
|
|
||||||
Brought to you by <img src="https://komodor.com/wp-content/uploads/2021/05/favicon.png"
|
|
||||||
style="height: 1rem"> <a class="me-4"
|
|
||||||
href="https://komodor.io">Komodor.io</a>
|
|
||||||
<i class="bi-github"></i>
|
|
||||||
<a href="https://github.com/komodorio/helm-dashboard">Project page on GitHub</a>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-3"></div>
|
<button type="button" class="btn-close text-reset" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
<div class="offcanvas-body p-2 ps-4" id="describeModalBody">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<img src="static/topographic.svg" class="position-absolute bottom-0 left-0" style="z-index: -100; height: 100%;"/>
|
|
||||||
|
|
||||||
|
<div class="modal" id="confirmModal" tabindex="-1" aria-labelledby="describeModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="confirmModalLabel"></h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body" id="confirmModalBody">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary btn-confirm">Confirm</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal" id="upgradeModal" tabindex="-1" aria-labelledby="describeModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h4 class="modal-title" id="upgradeModalLabel">
|
||||||
|
Upgrade <b class='text-success name'></b>
|
||||||
|
</h4>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<form class="modal-body border-bottom fs-5" enctype="multipart/form-data">
|
||||||
|
<div class="input-group mb-3 text-muted">
|
||||||
|
<label class="form-label me-4 text-dark">Version to install: <select
|
||||||
|
class='fw-bold text-success ver-new'></select></label> (current version is <span
|
||||||
|
class='text-success ver-old ms-1'>0.0.0</span>)
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 pe-3">
|
||||||
|
<label class="form-label">User-Defined Values:</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 ps-3">
|
||||||
|
<label class="form-label">Chart Values Reference:</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 pe-3">
|
||||||
|
<textarea name="values" class="form-control w-100 h-100" rows="5"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 ps-3">
|
||||||
|
<pre class="ref-vals fs-6 w-100 bg-secondary p-2 rounded" style="max-height: 20rem"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 pe-3">
|
||||||
|
<span class="invalid-feedback small mb-3"> (wrong YAML)</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="form-label mt-5">Manifest changes:</label>
|
||||||
|
<div id="upgradeModalBody" class="small"></div>
|
||||||
|
</form>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary btn-confirm">Confirm Upgrade</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js"
|
||||||
integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa"
|
integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa"
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
@@ -286,6 +333,9 @@
|
|||||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/luxon@3.0.3/build/global/luxon.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/luxon@3.0.3/build/global/luxon.min.js"
|
||||||
integrity="sha256-RH4TKnKcKyde0s2jc5BW3pXZl/5annY3fcZI9VrV5WQ=" crossorigin="anonymous"></script>
|
integrity="sha256-RH4TKnKcKyde0s2jc5BW3pXZl/5annY3fcZI9VrV5WQ=" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js"
|
||||||
|
integrity="sha512-CSBhVREyzHAjAFfBlIBakjoRUKp5h7VSweP0InR/pAJyptH7peuhCsqAI/snV+TwZmXZqoUklpXp6R6wMnYf5Q=="
|
||||||
|
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||||
<script src="static/list-view.js"></script>
|
<script src="static/list-view.js"></script>
|
||||||
<script src="static/revisions-view.js"></script>
|
<script src="static/revisions-view.js"></script>
|
||||||
<script src="static/details-view.js"></script>
|
<script src="static/details-view.js"></script>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<svg width="53" height="13" viewBox="0 0 53 13" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="77" height="19" viewBox="0 0 77 19" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<g clip-path="url(#clip0_36_1097)">
|
<g clip-path="url(#clip0_231_1524)">
|
||||||
<path d="M5.86295 4.90639H4.0672L1.355 7.52039V2.1665H0V11.0071H1.355V8.93513L2.42853 7.98768L4.57123 11.0071H6.23823L3.39732 7.11706L5.86295 4.90639Z" fill="#1347FF"/>
|
<path d="M8.51786 7.17108H5.90895L1.96859 10.9915V3.16663H0V16.0875H1.96859V13.0592L3.52824 11.6745L6.64123 16.0875H9.06309L4.93573 10.4021L8.51786 7.17108Z" fill="#1347FF"/>
|
||||||
<path d="M19.4109 4.90652L18.7279 5.58935L18.0559 4.90652H15.6142L14.8004 5.74086V4.90652H13.5217V11.0073H14.8789V6.73737L15.6011 6.01827H17.1634L17.5387 6.38531V11.0073H18.8959V6.49839L19.3847 6.01827H21.1804L21.5557 6.38531V11.0073H22.9108V5.99267L21.8263 4.90652H19.4109Z" fill="#1347FF"/>
|
<path d="M28.2007 7.17127L27.2085 8.16924L26.2321 7.17127H22.6848L21.5024 8.39069V7.17127H19.6448V16.0877H21.6165V9.84712L22.6658 8.79613H24.9356L25.4808 9.33257V16.0877H27.4526V9.49785L28.1627 8.79613H30.7716L31.3168 9.33257V16.0877H33.2854V8.75871L31.7099 7.17127H28.2007Z" fill="#1347FF"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.46283 5.99267L7.54726 4.90652H11.2544L12.3389 5.99267V9.92112L11.2544 11.0073H7.54726L6.46283 9.92112V5.99267ZM10.6086 9.89549L10.9708 9.52848H10.973V6.38531L10.6107 6.01827H8.19531L7.8331 6.38531V9.52848L8.19315 9.89549H10.6086Z" fill="#1347FF"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.38939 8.75871L10.9649 7.17127H16.3508L17.9263 8.75871V14.5003L16.3508 16.0877H10.9649L9.38939 14.5003V8.75871ZM15.4124 14.4628L15.9387 13.9264H15.9418V9.33257L15.4156 8.79613H11.9064L11.3802 9.33257V13.9264L11.9033 14.4628H15.4124Z" fill="#1347FF"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M43.2791 4.90652L42.1947 5.99267V9.92112L43.2791 11.0073H46.9863L48.0707 9.92112V5.99267L46.9863 4.90652H43.2791ZM46.7028 9.52848L46.3405 9.89549H43.9252L43.5629 9.52848V6.38531L43.9252 6.01827H46.3405L46.7028 6.38531V9.52848Z" fill="#1347FF"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M62.8772 7.17127L61.3017 8.75871V14.5003L62.8772 16.0877H68.2631L69.8385 14.5003V8.75871L68.2631 7.17127H62.8772ZM67.8513 13.9264L67.3249 14.4628H63.8158L63.2895 13.9264V9.33257L63.8158 8.79613H67.3249L67.8513 9.33257V13.9264Z" fill="#1347FF"/>
|
||||||
<path d="M50.5323 5.81555L51.4355 4.90652H53.0002V6.16979H51.3332L50.6108 6.8782V11.0073H49.2536V4.90652H50.5323V5.81555Z" fill="#1347FF"/>
|
<path d="M73.4148 8.49984L74.7271 7.17127H77.0003V9.01758H74.5785L73.5289 10.053V16.0877H71.5571V7.17127H73.4148V8.49984Z" fill="#1347FF"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.0906 4.90652L24.0061 5.99267V9.92112L25.0906 11.0073H33.1027L34.1871 9.92112V5.99267L33.1027 4.90652H25.0906ZM32.817 9.52848L32.4547 9.89549H25.7343L25.3721 9.52848V6.38531L25.7343 6.01827H32.4547L32.817 6.38531V9.52848Z" fill="#1347FF"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M36.4523 7.17127L34.8768 8.75871V14.5003L36.4523 16.0877H48.0926L49.668 14.5003V8.75871L48.0926 7.17127H36.4523ZM47.6776 13.9264L47.1512 14.4628H37.3875L36.8613 13.9264V9.33257L37.3875 8.79613H47.1512L47.6776 9.33257V13.9264Z" fill="#1347FF"/>
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.895 4.90639L39.6437 5.66392V2.1665H40.9984V11.0071H39.6437V10.2624L38.895 11.0071H36.3116L35.2272 9.92098V5.99252L36.3116 4.90639H38.895ZM38.9213 9.89538L39.6437 9.18905V6.73725L38.9213 6.01813H36.9577L36.5823 6.38516V9.52834L36.9577 9.89538H38.9213Z" fill="#1347FF"/>
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M56.5078 7.17108L57.5955 8.27823V3.16663H59.5637V16.0875H57.5955V14.9991L56.5078 16.0875H52.7546L51.1792 14.5001V8.7585L52.7546 7.17108H56.5078ZM56.546 14.4627L57.5955 13.4303V9.84695L56.546 8.79592H53.6933L53.1478 9.33236V13.9262L53.6933 14.4627H56.546Z" fill="#1347FF"/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
<clipPath id="clip0_36_1097">
|
<clipPath id="clip0_231_1524">
|
||||||
<rect width="53" height="13" fill="white"/>
|
<rect width="77" height="19" fill="white"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
</defs>
|
</defs>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,4 +1,5 @@
|
|||||||
function loadChartsList() {
|
function loadChartsList() {
|
||||||
|
$("body").removeClass("bg-variant1 bg-variant2").addClass("bg-variant1")
|
||||||
$("#sectionList").show()
|
$("#sectionList").show()
|
||||||
const chartsCards = $("#installedList .body")
|
const chartsCards = $("#installedList .body")
|
||||||
chartsCards.empty().append("<div><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span> Loading...</div>")
|
chartsCards.empty().append("<div><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span> Loading...</div>")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const revRow = $("#sectionDetails .rev-list ul");
|
const revRow = $("#sectionDetails .rev-list ul");
|
||||||
|
|
||||||
function loadChartHistory(namespace, name) {
|
function loadChartHistory(namespace, name) {
|
||||||
|
$("body").removeClass("bg-variant1 bg-variant2").addClass("bg-variant2")
|
||||||
$("#sectionDetails").show()
|
$("#sectionDetails").show()
|
||||||
$("#sectionDetails .name").text(name)
|
$("#sectionDetails .name").text(name)
|
||||||
revRow.empty().append("<li><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span></li>")
|
revRow.empty().append("<li><span class=\"spinner-border spinner-border-sm\" role=\"status\" aria-hidden=\"true\"></span></li>")
|
||||||
@@ -25,10 +26,10 @@ function fillChartHistory(data, namespace, name) {
|
|||||||
data.reverse()
|
data.reverse()
|
||||||
for (let x = 0; x < data.length; x++) {
|
for (let x = 0; x < data.length; x++) {
|
||||||
const elm = data[x]
|
const elm = data[x]
|
||||||
$("#specRev").val(elm.revision).data("first-rev", elm.revision)
|
$("#specRev").data("first-rev", elm.revision)
|
||||||
|
|
||||||
if (!x) {
|
if (!x) {
|
||||||
$("#specRev").data("last-rev", elm.revision).data("last-chart-ver", elm.chart_ver)
|
$("#specRev").val(elm.revision).data("last-rev", elm.revision).data("last-chart-ver", elm.chart_ver)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rev = $(`<li class="px-2 pt-5 pb-4 mb-2 rounded border border-secondary bg-secondary position-relative">
|
const rev = $(`<li class="px-2 pt-5 pb-4 mb-2 rounded border border-secondary bg-secondary position-relative">
|
||||||
@@ -45,6 +46,11 @@ function fillChartHistory(data, namespace, name) {
|
|||||||
rev.find(".rev-age").text(getAge(elm, data[x - 1])).parent().attr("title", elm.updated)
|
rev.find(".rev-age").text(getAge(elm, data[x - 1])).parent().attr("title", elm.updated)
|
||||||
statusStyle(elm.status, rev.find(".rev-status"), rev.find(".rev-status"))
|
statusStyle(elm.status, rev.find(".rev-status"), rev.find(".rev-status"))
|
||||||
|
|
||||||
|
if (elm.description.startsWith("Rollback to ")) {
|
||||||
|
//rev.find(".rev-status").append(" <span class='small fw-normal text-lowercase'>(rollback)</span>")
|
||||||
|
rev.find(".rev-status").append(" <i class='bi-arrow-counterclockwise text-muted' title='"+elm.description+"'></i>")
|
||||||
|
}
|
||||||
|
|
||||||
const nxt = data[x + 1];
|
const nxt = data[x + 1];
|
||||||
if (nxt && isNewerVersion(elm.chart_ver, nxt.chart_ver)) {
|
if (nxt && isNewerVersion(elm.chart_ver, nxt.chart_ver)) {
|
||||||
rev.find(".rev-changes").html("<span class='strike'>" + nxt.chart_ver + "</span> <i class='text-danger bi-arrow-down-right'></i> " + elm.chart_ver)
|
rev.find(".rev-changes").html("<span class='strike'>" + nxt.chart_ver + "</span> <i class='text-danger bi-arrow-down-right'></i> " + elm.chart_ver)
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ $(function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const myAlert = document.getElementById('errorAlert')
|
||||||
|
myAlert.addEventListener('close.bs.alert', event => {
|
||||||
|
event.preventDefault()
|
||||||
|
$("#errorAlert").hide()
|
||||||
|
})
|
||||||
|
|
||||||
function reportError(err, xhr) {
|
function reportError(err, xhr) {
|
||||||
$("#errorAlert h4 span").text(err)
|
$("#errorAlert h4 span").text(err)
|
||||||
if (xhr) {
|
if (xhr) {
|
||||||
@@ -62,6 +68,20 @@ function statusStyle(status, card, txt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCleanClusterName(rawClusterName) {
|
||||||
|
if (rawClusterName.indexOf('arn')==0) {
|
||||||
|
// AWS cluster
|
||||||
|
clusterSplit = rawClusterName.split(':')
|
||||||
|
clusterName = clusterSplit.at(-1).split("/").at(-1)
|
||||||
|
region = clusterSplit.at(-3)
|
||||||
|
return region + "/" + clusterName + ' [AWS]'
|
||||||
|
}
|
||||||
|
if (rawClusterName.indexOf('gke')==0) {
|
||||||
|
// GKE cluster
|
||||||
|
return rawClusterName.split('_').at(-2) + '/' + rawClusterName.split('_').at(-1) + ' [GKE]'
|
||||||
|
}
|
||||||
|
return rawClusterName
|
||||||
|
}
|
||||||
|
|
||||||
function fillClusterList(data, context) {
|
function fillClusterList(data, context) {
|
||||||
data.forEach(function (elm) {
|
data.forEach(function (elm) {
|
||||||
@@ -71,7 +91,7 @@ function fillClusterList(data, context) {
|
|||||||
let opt = $('<li><label><input type="radio" name="cluster" class="me-2"/><span></span></label></li>');
|
let opt = $('<li><label><input type="radio" name="cluster" class="me-2"/><span></span></label></li>');
|
||||||
opt.attr('title', label)
|
opt.attr('title', label)
|
||||||
opt.find("input").val(elm.Name).text(label)
|
opt.find("input").val(elm.Name).text(label)
|
||||||
opt.find("span").text(label)
|
opt.find("span").text(getCleanClusterName(label))
|
||||||
if (elm.IsCurrent && !context) {
|
if (elm.IsCurrent && !context) {
|
||||||
opt.find("input").prop("checked", true)
|
opt.find("input").prop("checked", true)
|
||||||
setCurrentContext(elm.Name)
|
setCurrentContext(elm.Name)
|
||||||
|
|||||||
@@ -46,19 +46,37 @@
|
|||||||
color: #333333 !important;
|
color: #333333 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-danger {
|
||||||
|
background-color: #FC1683 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-success {
|
||||||
|
background-color: #A4F8D7 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #1347FF;
|
||||||
|
}
|
||||||
|
|
||||||
.text-uppercase {
|
.text-uppercase {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.offcanvas {
|
.offcanvas {
|
||||||
width: auto!important;
|
width: auto !important;
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
min-width: 60%;
|
min-width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
height: 100%;
|
|
||||||
min-height: 50rem;
|
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
||||||
background-color: #F4F7FA;
|
background-color: #F4F7FA;
|
||||||
@@ -66,6 +84,18 @@ body {
|
|||||||
color: #3D4048;
|
color: #3D4048;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.bg-variant1 {
|
||||||
|
background-color: #F4F7FA;
|
||||||
|
background-image: url("topographic.svg");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: bottom left;
|
||||||
|
background-size: auto 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.bg-variant2 {
|
||||||
|
background-color: #E8EDF2;
|
||||||
|
}
|
||||||
|
|
||||||
body > .container-fluid {
|
body > .container-fluid {
|
||||||
min-height: 100% !important;
|
min-height: 100% !important;
|
||||||
}
|
}
|
||||||
@@ -73,32 +103,11 @@ body > .container-fluid {
|
|||||||
#topNav.navbar {
|
#topNav.navbar {
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand > div {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand > div p span {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-family: Poppins, serif;
|
font-family: Poppins, serif;
|
||||||
font-size: 0.6rem !important;
|
font-size: 0.6rem !important;
|
||||||
color: #707583;
|
color: #707583;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand h1 {
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar-brand h1 a {
|
|
||||||
font-size: 1.1rem !important;
|
|
||||||
color: #0023A3 !important;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,17 +118,16 @@ body > .container-fluid {
|
|||||||
margin: 0.5rem 0.8rem
|
margin: 0.5rem 0.8rem
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand p {
|
.navbar-brand > div {
|
||||||
margin-bottom: 0;
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand p a {
|
.navbar-brand h1 a {
|
||||||
vertical-align: text-bottom;
|
font-size: 1.2rem !important;
|
||||||
}
|
color: #0023A3 !important;
|
||||||
|
text-decoration: none;
|
||||||
.navbar-brand p img {
|
vertical-align: middle;
|
||||||
height: 1.25rem;
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#topNav .navbar i.btn {
|
#topNav .navbar i.btn {
|
||||||
@@ -264,6 +272,10 @@ span.link {
|
|||||||
color: #707583;
|
color: #707583;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#actionButtons .link {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
#actionButtons button > * {
|
#actionButtons button > * {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
@@ -279,9 +291,62 @@ span.link {
|
|||||||
|
|
||||||
.nav-tabs .nav-link {
|
.nav-tabs .nav-link {
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
|
color: #3B3D45;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-tabs .nav-link.active {
|
.nav-tabs .nav-link.active {
|
||||||
border: none;
|
border: none;
|
||||||
border-bottom: 3px solid #3B3D45;
|
border-bottom: 3px solid #3B3D45;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#installedList .b-shadow:hover {
|
||||||
|
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnUpgradeCheck {
|
||||||
|
color: #3B3D45;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btnUpgrade {
|
||||||
|
min-width: 9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sectionDetails > .bg-white {
|
||||||
|
background-color: #F4F7FA !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sectionDetails .list-unstyled .bg-secondary {
|
||||||
|
background-color: #F4F7FA !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-resources .badge {
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-resources .bg-secondary {
|
||||||
|
background-color: #E6E7EB!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.res-actions .btn-sm {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offcanvas-header h5 {
|
||||||
|
font-family: Poppins, serif;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offcanvas-header h5 .badge {
|
||||||
|
font-family: Roboto, serif;
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.offcanvas-header p {
|
||||||
|
font-family: Inter, serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#describeModalBody pre {
|
||||||
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,8 @@ package dashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,4 +16,18 @@ func chartAndVersion(x string) (string, string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return x[:lastInd], x[lastInd+1:], nil
|
return x[:lastInd], x[lastInd+1:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tempFile(txt string) (string, func(), error) {
|
||||||
|
file, err := ioutil.TempFile("", "helm_vals_")
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile(file.Name(), []byte(txt), 0600)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Name(), func() { os.Remove(file.Name()) }, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
name: "dashboard"
|
name: "dashboard"
|
||||||
version: "0.0.6"
|
version: "0.1.1"
|
||||||
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"
|
||||||
|
|||||||
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 270 KiB |
Reference in New Issue
Block a user