mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
Values editing (#17)
* Refactorings * save * REdesigning install dialog * Display values editor * Reconfigure flow * Status handler and version * error reporting
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
"helm.sh/helm/v3/pkg/release"
|
"helm.sh/helm/v3/pkg/release"
|
||||||
"io/ioutil"
|
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/testapigroup/v1"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -234,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
|
||||||
@@ -242,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
|
||||||
@@ -252,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
|
||||||
@@ -289,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(),
|
||||||
@@ -305,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
|
||||||
@@ -367,24 +366,22 @@ 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{"upgrade", name, repoChart, "--version", version, "--namespace", namespace, "--values", file.Name(), "--output", "json"}
|
|
||||||
if justTemplate {
|
if justTemplate {
|
||||||
cmd = append(cmd, "--dry-run")
|
cmd = append(cmd, "--dry-run")
|
||||||
}
|
}
|
||||||
@@ -406,11 +403,22 @@ func (d *DataLayer) ChartUpgrade(namespace string, name string, repoChart string
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
out = getDiff(strings.TrimSpace(manifests), strings.TrimSpace(res.Manifest), "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)
|
||||||
@@ -438,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
|
||||||
|
|||||||
@@ -4,7 +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-check-circle").addClass("bi-hourglass-split")
|
$("#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 () {
|
||||||
@@ -29,24 +29,30 @@ function checkUpgradeable(name) {
|
|||||||
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")
|
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-arrow-up")
|
||||||
} else {
|
} else {
|
||||||
$("#btnUpgrade span").text("Up-to-date")
|
$("#btnUpgrade span").text("Reconfigure")
|
||||||
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-check-circle")
|
$("#btnUpgrade .icon").removeClass("bi-hourglass-split").addClass("bi-pencil")
|
||||||
}
|
}
|
||||||
|
|
||||||
$("#btnUpgrade").off("click").click(function () {
|
$("#btnUpgrade").off("click").click(function () {
|
||||||
@@ -58,12 +64,12 @@ 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.Modal(document.getElementById('upgradeModal'), {});
|
const myModal = new bootstrap.Modal(document.getElementById('upgradeModal'), {});
|
||||||
myModal.show()
|
myModal.show()
|
||||||
@@ -72,26 +78,86 @@ function popUpUpgrade(self, verCur, elm) {
|
|||||||
btnConfirm.prop("disabled", true).off('click').click(function () {
|
btnConfirm.prop("disabled", true).off('click').click(function () {
|
||||||
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) {
|
||||||
|
console.log(data)
|
||||||
|
if (data.version) {
|
||||||
setHashParam("revision", data.version)
|
setHashParam("revision", data.version)
|
||||||
window.location.reload()
|
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');
|
||||||
@@ -101,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 () {
|
||||||
|
|||||||
@@ -4,15 +4,22 @@
|
|||||||
n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,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')
|
})(window,document,'script','https://www.datadoghq-browser-agent.com/datadog-rum-v4.js','DD_RUM')
|
||||||
DD_RUM.onReady(function() {
|
DD_RUM.onReady(function() {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||||
DD_RUM.init({
|
DD_RUM.init({
|
||||||
clientToken: 'pub16d64cd1c00cf073ce85af914333bf72',
|
clientToken: 'pub16d64cd1c00cf073ce85af914333bf72',
|
||||||
applicationId: 'e75439e5-e1b3-46ba-a9e9-a2e58579a2e2',
|
applicationId: 'e75439e5-e1b3-46ba-a9e9-a2e58579a2e2',
|
||||||
site: 'datadoghq.com',
|
site: 'datadoghq.com',
|
||||||
service: 'helm-dashboard',
|
service: 'helm-dashboard',
|
||||||
version: 'v0.0.0',
|
version: xhr.responseText,
|
||||||
trackInteractions: true,
|
trackInteractions: true,
|
||||||
trackResources: true,
|
trackResources: true,
|
||||||
trackLongTasks: true,
|
trackLongTasks: true,
|
||||||
defaultPrivacyLevel: 'mask'
|
defaultPrivacyLevel: 'mask'
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.open('GET', '/status', true);
|
||||||
|
xhr.send(null);
|
||||||
})
|
})
|
||||||
@@ -16,7 +16,18 @@
|
|||||||
<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">
|
<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])};
|
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");
|
heap.load("3615793373");
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
@@ -52,9 +63,11 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn" href="https://komodor.com/?utm_campaign=Helm-Dash&utm_source=helm-dash"><img
|
<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>
|
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>
|
<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>
|
||||||
<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>
|
||||||
@@ -222,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>
|
||||||
@@ -268,15 +281,42 @@
|
|||||||
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
|
<div class="modal-dialog modal-dialog modal-dialog-scrollable modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h5 class="modal-title" id="upgradeModalLabel">
|
<h4 class="modal-title" id="upgradeModalLabel">
|
||||||
Upgrade <b class='text-success name'></b> from version <b class='text-success ver-old'></b> to
|
Upgrade <b class='text-success name'></b>
|
||||||
<select class='fw-bold text-success ver-new'></select>
|
</h4>
|
||||||
</h5>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" id="upgradeModalBody">
|
<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>
|
||||||
|
<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">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-primary btn-confirm">Confirm Upgrade</button>
|
<button type="button" class="btn btn-primary btn-confirm">Confirm Upgrade</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -293,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>
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package dashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,3 +17,17 @@ 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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user