Files
helm-dashboard/pkg/dashboard/static/details-view.js
Andrey Pokhilko e13aa2fde6 [WIP] Major release 1.0 (#147)
* Object model with self-sufficient binary (#131)

* Code cosmetics

* Experimenting with object model and direct HELM usage

* Experiment with object model

* replacing the kubectl

* Progressing

* Save the progress

* Able to start with migration in mind

* Migrated two pieces

* List releases via Helm

* Forgotten field

* Cristallized the problem of ctx switcher

* Reworked to multi-context

* Rollback is also new style

* More migration

* Refactoring

* Describe via code

* Bye-bye kubectl binary

* Eliminate more old code

* Refactor a bit

* Merges

* No binaries in dockerfile

* Commit

* Progress with getting the data

* Learned the thing about get

* One field less

* Sstart with repos

* Repo add

* repo remove

* Repos! Icons!

* Simplified access to data

* Ver listing works

* Ver check works

* Caching and values

* fixup

* Done with repos

* Working on install

* Install work-ish

* Fix UI failing on install

* Upgrade flow works

* Fix image building

* Remove outdated test file

* Move files around

* REfactorings

* Cosmetics

* Test for cache control (#151)

* Files import formatted

* Added go-test tools

* Added test for no-cache header

* added changes

* test for cache behaviour of app

* test for static route (#153)

* Tests: route configuration & context setter (#154)

* Test for route configuration

* Test for context setter middleware

* implemented changes

* Restore coverage profile

Fixes #156

* Cosmetics

* Test for `NewRouter` function (#157)

* Test for `configureScanners` (#158)

* Test for `configureKubectls` (#163)

* Test for repository loading (#169)

- Created `repos_test.go`
- Test: `Load()` of Repositories

* Build all PRs

* Fixes failing test (#171)

* Fixes failing test
- Fixes failing test of repo loading

* handles error for

* Did some changes

* Test for listing of repos (#173)

- and did some code formatting

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

* Test for adding repo (#175)

- Modified the `repositories.yml` file

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>

* Test for deleting the repository (#176)

* Test for deleting the repository
- Also added cleanup function for `TestAdd`

* Fixes failing test

* Add auto labeler for PR's (#174)

* Add auto labeler for PR's

* Add all file under .github/workflow to 'ci' label

Co-authored-by: Harshit Mehta <harshitm@nvidia.com>

* Test for getting repository (#177)

* Add github workflow for auto PR labeling (#181)

Co-authored-by: Harshit Mehta <harshitm@nvidia.com>

* Stub compilation

* Fixes around installing

* More complex test

* Using object model to execute helm test (#191)

* Expand test

* More test

* Coverage

* Add mutex for operations

* Rectore cluster detection code

* Change receiver to pointer

* Support multiple namespaces

* Cosmetics

* Update repos periodically

* fix tests

* Fix error display

* Allow reconfiguring chart without repo

* mute  linter

* Cosmetics

* Failing approach to parse manifests

Relates to #30

* Report the error properly

*  Add test for dashboard/objects/data.go NewDataLayer (#199)

* Fix problem of wrong namespace

* Added unit tests for releases (#204)

* Rework API routes (#197)

* Bootstrap OpenAPI doc

* Renaming some routes

* Listing namespaces

* k8s part of things

* Repositories section

* Document scanners API

* One more API call

* Progress

* Reworked install flow

* History endpoint

* Textual info section

* Resources endpoint

* Rollback endpoint

* Rollback endpoint

* Unit tests

* Cleanup

* Forgotten tags

* Fix tests

* TODOs

* Rework manifest scanning

* add hasTests flag

* Adding more information on UI for helm test API response (#195)

* Hide test button when no tests

Fixes #115
Improves #195

---------

Signed-off-by: OmAxiani0 <aximaniom@gmail.com>
Co-authored-by: Om Aximani <75031769+OmAximani0@users.noreply.github.com>
Co-authored-by: Harshit Mehta <hdm23061993@gmail.com>
Co-authored-by: Harshit Mehta <harshitm@nvidia.com>
Co-authored-by: Todd Turner <todd@toddtee.sh>
Co-authored-by: arvindsundararajan98 <109727359+arvindsundararajan98@users.noreply.github.com>
2023-02-01 13:24:34 +00:00

297 lines
12 KiB
JavaScript

function revisionClicked(namespace, name, self) {
let active = "active border-primary border-1 bg-white";
let inactive = "border-secondary bg-secondary";
revRow.find(".active").removeClass(active).addClass(inactive)
self.removeClass(inactive).addClass(active)
const elm = self.data("elm")
setHashParam("revision", elm.revision)
$("#sectionDetails span.rev").text("#" + elm.revision)
statusStyle(elm.status, $("#none"), $("#sectionDetails .rev-details .rev-status"))
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-app").text(elm.app_version)
$("#sectionDetails .rev-tags .rev-ns").text(getHashParam("namespace"))
if (getHashParam("context")) {
$("#sectionDetails .rev-tags .rev-cluster").text(getHashParam("context"))
} else {
$("#sectionDetails .rev-tags .rev-cluster").parent().hide() // TODO: makes UI jumpy, change to showing
}
$("#revDescr").text(elm.description).removeClass("text-danger")
if (elm.status === "failed") {
$("#revDescr").addClass("text-danger")
}
const rev = $("#specRev").data("last-rev") == elm.revision ? elm.revision - 1 : elm.revision
if (!rev || getHashParam("revision") === $("#specRev").data("first-rev")) {
$("#btnRollback").hide()
} else {
$("#btnRollback").show().data("rev", rev).find("span").text("Rollback to #" + rev)
}
const tab = getHashParam("tab")
if (!tab) {
$("#nav-tab [data-tab=resources]").click()
} else {
$("#nav-tab [data-tab=" + tab + "]").click()
}
}
function loadContentWrapper() {
let revDiff = 0
const revision = parseInt(getHashParam("revision"));
if (revision === $("#specRev").data("first-rev")) {
revDiff = 0
} else if (getHashParam("mode") === "diff-prev") {
revDiff = revision - 1
} else if (getHashParam("mode") === "diff-rev") {
revDiff = $("#specRev").val()
}
const flag = $("#userDefinedVals").prop("checked");
loadContent(getHashParam("tab"), getHashParam("namespace"), getHashParam("chart"), revision, revDiff, flag)
}
function loadContent(mode, namespace, name, revision, revDiff, userDefined) {
let qstr = "revision=" + revision
if (revDiff) {
qstr += "&revisionDiff=" + revDiff
}
if (userDefined) {
qstr += "&userDefined=" + userDefined
}
let url = "/api/helm/releases/" + namespace + "/" + name + "/" + mode
url += "?" + qstr
const diffDisplay = $("#manifestText");
diffDisplay.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>')
$.get(url).fail(function (xhr) {
reportError("Failed to get diff of " + mode, xhr)
}).done(function (data) {
diffDisplay.empty();
if (data === "") {
diffDisplay.text("No differences to display")
} else {
if (revDiff) {
const targetElement = document.getElementById('manifestText');
const configuration = {
inputFormat: 'diff', outputFormat: 'side-by-side',
drawFileList: false, showFiles: false, highlight: true, //matching: 'lines',
};
const diff2htmlUi = new Diff2HtmlUI(targetElement, data, configuration);
diff2htmlUi.draw()
} else {
data = hljs.highlight(data, {language: 'yaml'}).value
const code = $("#manifestText").empty().append("<pre class='bg-white rounded p-3'></pre>").find("pre");
code.html(data)
}
}
})
}
$('#specRev').keyup(function (event) {
let keycode = (event.keyCode ? event.keyCode : event.which);
if (keycode == '13') {
$("#diffModeRev").click()
}
});
$("form").submit(function (e) {
e.preventDefault();
});
$("#userDefinedVals").change(function () {
const self = $(this)
const flag = $("#userDefinedVals").prop("checked");
setHashParam("udv", flag)
loadContentWrapper()
})
$("#modePanel [data-mode]").click(function () {
const self = $(this)
const mode = self.data("mode")
setHashParam("mode", mode)
loadContentWrapper()
})
$("#nav-tab [data-tab]").click(function () {
const self = $(this)
setHashParam("tab", self.data("tab"))
if (self.data("tab") === "values") {
$("#userDefinedVals").parent().show()
} else {
$("#userDefinedVals").parent().hide()
}
const flag = getHashParam("udv") === "true";
$("#userDefinedVals").prop("checked", flag)
if (self.data("tab") === "resources") {
showResources(getHashParam("namespace"), getHashParam("chart"), getHashParam("revision"))
} else {
const mode = getHashParam("mode")
if (!mode) {
$("#modePanel [data-mode=view]").trigger('click')
} else {
$("#modePanel [data-mode=" + mode + "]").trigger('click')
}
}
})
function showResources(namespace, chart, revision) {
const resBody = $("#nav-resources .body");
const interestingResources = ["STATEFULSET", "DEAMONSET", "DEPLOYMENT"];
resBody.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>');
let url = "/api/helm/releases/" + namespace + "/" + chart + "/resources"
$.getJSON(url).fail(function (xhr) {
reportError("Failed to get list of resources", xhr)
}).done(function (data) {
const scanners = $("body").data("scanners");
const scannableResKinds = new Set();
for (let k in scanners) {
scanners[k].SupportedResourceKinds.forEach(scannableResKinds.add, scannableResKinds)
}
resBody.empty();
data = data.sort(function (a, b) {
return interestingResources.indexOf(a.kind.toUpperCase()) - interestingResources.indexOf(b.kind.toUpperCase())
}).reverse();
for (let i = 0; i < data.length; i++) {
const res = data[i]
const resBlock = $(`
<div class="row px-3 py-2 mb-3 bg-white rounded">
<div class="col-2 res-kind text-break"></div>
<div class="col-3 res-name text-break fw-bold"></div>
<div class="col-2 res-status overflow-hidden"><span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span></div>
<div class="col-3 res-statusmsg text-break"><span class="text-muted small">Getting status...</span></div>
<div class="col-2 res-actions"><button class='btn btn-sm ms-2 visually-hidden'>Vertical-sizer</button></div>
</div>
`)
resBlock.find(".res-kind").text(res.kind)
resBlock.find(".res-name").text(res.metadata.name)
resBody.append(resBlock)
let ns = res.metadata.namespace ? res.metadata.namespace : namespace
$.getJSON("/api/k8s/" + res.kind.toLowerCase() + "/get?name=" + res.metadata.name + "&namespace=" + ns).fail(function () {
//reportError("Failed to get list of resources")
}).done(function (data) {
const badge = $("<span class='badge me-2 fw-normal'></span>").text(data.status.phase);
if (["Available", "Active", "Established", "Bound", "Ready"].includes(data.status.phase)) {
badge.addClass("bg-success text-dark")
} else if (["Exists"].includes(data.status.phase)) {
badge.addClass("bg-success text-dark bg-opacity-50")
} else if (["Progressing"].includes(data.status.phase)) {
badge.addClass("bg-warning")
} else {
badge.addClass("bg-danger")
}
const statusBlock = resBlock.find(".res-status");
statusBlock.empty().append(badge).attr("title", data.status.phase)
const statusMessage = getStatusMessage(data.status)
resBlock.find(".res-statusmsg").html("<span class='text-muted small'>" + (statusMessage ? statusMessage : '') + "</span>")
if (badge.text() !== "NotFound" && revision == $("#specRev").data("last-rev")) {
resBlock.find(".res-actions")
const btn = $("<button class=\"btn btn-sm btn-white border-secondary\">Describe</button>");
resBlock.find(".res-actions").append(btn)
btn.click(function () {
showDescribe(ns, res.kind, res.metadata.name, badge.clone())
})
if (scannableResKinds.has(res.kind)) {
const btn2 = $("<button class='btn btn-sm btn-white border-secondary ms-2'>Scan</button>");
resBlock.find(".res-actions").append(btn2)
btn2.click(function () {
scanResource(ns, res.kind, res.metadata.name, badge.clone())
})
}
}
})
}
})
}
function getStatusMessage(status) {
if (!status) {
return
}
if (status.conditions) {
return status.conditions[0].message || status.conditions[0].reason
}
return status.message || status.reason
}
function showDescribe(ns, kind, name, badge) {
$("#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>')
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
myModal.show()
$.get("/api/k8s/" + kind.toLowerCase() + "/describe?name=" + name + "&namespace=" + ns).fail(function (xhr) {
reportError("Failed to describe resource", xhr)
}).done(function (data) {
data = hljs.highlight(data, {language: 'yaml'}).value
$("#describeModalBody").empty().append("<pre class='bg-white rounded p-3'></pre>").find("pre").html(data)
})
}
function scanResource(ns, kind, name, badge) {
$("#describeModal .offcanvas-header p").text(kind)
$("#describeModalLabel").text(name).append(badge.addClass("ms-3 small fw-normal"))
const body = $("#describeModalBody");
body.empty().append('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Scanning...')
const myModal = new bootstrap.Offcanvas(document.getElementById('describeModal'));
myModal.show()
$.get("/api/scanners/resource/" + kind.toLowerCase() + "?name=" + name + "&namespace=" + ns).fail(function (xhr) {
reportError("Failed to scan resource", xhr)
}).done(function (data) {
body.empty()
if ($.isEmptyObject(data)) {
body.append("No information from scanners. Make sure you have installed some and scanned object is supported.")
}
const tabs = $('<ul class="nav nav-tabs mt-3" role="tablist"></ul>')
const content = $('<div class="tab-content"></div>')
for (let name in data) {
const res = data[name]
if (!res.OrigReport && !res.PassedCount) continue
const hdr = $(`<li class="nav-item" role="presentation">
<button class="nav-link" id="` + name + `-tab" data-bs-toggle="tab" data-bs-target="#` + name + `-tab-pane" type="button" role="tab">` + name + `</button>
</li>`)
if (res.FailedCount) {
hdr.find('button').append("<span class='badge bg-danger ms-2'>" + res.FailedCount + " failed</span>")
}
if (res.PassedCount) {
hdr.find('button').append("<span class='badge bg-info ms-2'>" + res.PassedCount + " passed</span>")
}
const hl = hljs.highlight(res.OrigReport, {language: 'yaml'}).value
const pre = $("<pre class='bg-white rounded p-3' style='font-size: inherit; overflow: unset'></pre>").html(hl)
const div = $('<div class="tab-pane fade" id="' + name + '-tab-pane" role="tabpanel" aria-labelledby="profile-tab" tabindex="0"></div>').append(pre)
tabs.append(hdr)
content.append(div)
}
body.append(tabs)
body.append(content)
tabs.find('li').first().find('button').click()
})
}