Filter installed charts by namespace (#101)

* filter by namespace

* exists

* Some improvements, one thing resolved

* cleanup

* merge

* allow filtering by name

* filter by namespace

* changes

* change url parameter name

* keep filtered namespaces when refreshing and combine inpt and namespace filtering

* Refactoring

* Cleanup

* Forced NS handle

* remove else

Co-authored-by: Andrei Pohilko <andrei.pokhilko@gmail.com>
This commit is contained in:
ronahk
2022-11-23 13:38:09 +02:00
committed by GitHub
parent bedb356b02
commit 3abae8e49e
5 changed files with 157 additions and 54 deletions

View File

@@ -130,12 +130,10 @@
<ul class="list-unstyled" id="cluster"> <ul class="list-unstyled" id="cluster">
</ul> </ul>
<h4 id="limitNamespace" class="display-none">Forced Namespace: <span></span></h4>
<!-- TODO
<h4 class="mt-4">Namespaces</h4> <h4 class="mt-4">Namespaces</h4>
<ul class="list-unstyled" id="namespaces"> <p id="limitNamespace" class="display-none ps-3"><span class="fw-bold"></span> (forced)</p>
<ul class="list-unstyled" id="namespace">
</ul> </ul>
-->
</form> </form>
</div> </div>
<!-- /FILTER BLOCK --> <!-- /FILTER BLOCK -->
@@ -145,9 +143,12 @@
<!-- INSTALLED LIST --> <!-- INSTALLED LIST -->
<div class="col ms-2" id="installedList"> <div class="col ms-2" id="installedList">
<div class="col rounded rounded-1 b-shadow header"> <div class="col rounded rounded-1 b-shadow header">
<div class="bg-white rounded-top m-0"> <div class="bg-white rounded-top m-0 spaced-out">
<h2 class="m-0 p-1"><img class="m-2 mx-3 me-2" src="static/helm-gray.svg" alt="Installed Charts">Installed <h2 class="m-0 p-1"><img class="m-2 mx-3 me-2" src="static/helm-gray.svg" alt="Installed Charts">Installed
Charts (<span></span>)</h2> Charts (<span></span>)</h2>
<div class="form-outline w-25">
<input type="text" id="installedSearch" class="form-control form-control-sm" placeholder="Filter..." />
</div>
</div> </div>
<div class="bg-secondary rounded-bottom m-0 row p-2"> <div class="bg-secondary rounded-bottom m-0 row p-2">
<div class="col-4 hdr-name">Name</div> <div class="col-4 hdr-name">Name</div>
@@ -163,6 +164,8 @@
<div class="bg-white rounded shadow p-3 display-none no-charts">Looks like you don't have any charts <div class="bg-white rounded shadow p-3 display-none no-charts">Looks like you don't have any charts
installed. "Repository" section may be a good place to start. installed. "Repository" section may be a good place to start.
</div> </div>
<div class="bg-white rounded shadow p-3 display-none all-filtered">There are no releases matching your filter criteria. Reset your filters or install more charts.
</div>
</div> </div>
<!-- /INSTALLED LIST --> <!-- /INSTALLED LIST -->
</div> </div>

View File

@@ -6,19 +6,21 @@ function loadChartsList() {
$.getJSON("/api/helm/charts").fail(function (xhr) { $.getJSON("/api/helm/charts").fail(function (xhr) {
reportError("Failed to get list of charts", xhr) reportError("Failed to get list of charts", xhr)
}).done(function (data) { }).done(function (data) {
chartsCards.empty() chartsCards.empty().hide()
$("#installedList .header h2 span").text(data.length) $("#installedList .header h2 span").text(data.length)
data.forEach(function (elm) { data.forEach(function (elm) {
let card = buildChartCard(elm); let card = buildChartCard(elm);
chartsCards.append(card) chartsCards.append(card)
}) })
filterInstalledList(chartsCards.find(".row"))
chartsCards.show()
if (!data.length) { if (!data.length) {
$("#installedList .no-charts").show() $("#installedList .no-charts").show()
} }
}) })
} }
function buildChartCard(elm) { function buildChartCard(elm) {
const card = $(`<div class="row m-0 py-4 bg-white rounded-1 b-shadow border-4 border-start"> const card = $(`<div class="row m-0 py-4 bg-white rounded-1 b-shadow border-4 border-start">
<div class="col-4 rel-name"><span class="link">release-name</span><div></div></div> <div class="col-4 rel-name"><span class="link">release-name</span><div></div></div>
@@ -38,7 +40,7 @@ function buildChartCard(elm) {
reportError("Failed to get list of charts", xhr) reportError("Failed to get list of charts", xhr)
}).done(function (data) { }).done(function (data) {
if (data) { if (data) {
var res = data[0] const res = data[0];
if (res.icon) { if (res.icon) {
card.find(".rel-name").attr("style", "background-image: url(" + res.icon + ")") card.find(".rel-name").attr("style", "background-image: url(" + res.icon + ")")
} }
@@ -57,6 +59,10 @@ function buildChartCard(elm) {
card.find(".rel-chart span").text(elm.chart) card.find(".rel-chart span").text(elm.chart)
card.find(".rel-date span").text(getAge(elm)) card.find(".rel-date span").text(getAge(elm))
card.data("namespace", elm.namespace)
card.data("name", elm.name)
card.data("chart", elm.chart)
statusStyle(elm.status, card, card.find(".rel-status span")) statusStyle(elm.status, card, card.find(".rel-status span"))
card.find("a").attr("href", '#context=' + getHashParam('context') + '&namespace=' + elm.namespace + '&name=' + elm.name) card.find("a").attr("href", '#context=' + getHashParam('context') + '&namespace=' + elm.namespace + '&name=' + elm.name)
@@ -74,3 +80,6 @@ function buildChartCard(elm) {
return card; return card;
} }
$("#installedSearch").keyup(function () {
filterInstalledList($("#installedList .body .row"))
})

View File

@@ -131,7 +131,7 @@ function repoChartClicked() {
window.location.reload() window.location.reload()
} else { } else {
const contexts = $("body").data("contexts") const contexts = $("body").data("contexts")
contextNamespace = contexts.filter(obj => {return obj.Name === getHashParam("context")})[0].Namespace const contextNamespace = contexts.filter(obj => {return obj.Name === getHashParam("context")})[0].Namespace
popUpUpgrade(elm, contextNamespace) popUpUpgrade(elm, contextNamespace)
} }
} }

View File

@@ -1,32 +1,15 @@
$(function () { $(function () {
const clusterSelect = $("#cluster"); let limNS = null
clusterSelect.change(function () { $.getJSON("/status").fail(function (xhr) { // maybe /options call in the future
window.location.href = "/#context=" + clusterSelect.find("input:radio:checked").val() reportError("Failed to get tool version", xhr)
window.location.reload()
})
$.getJSON("/api/kube/contexts").fail(function (xhr) {
reportError("Failed to get list of clusters", xhr)
}).done(function (data) { }).done(function (data) {
$("body").data("contexts", data) fillToolVersion(data)
const context = getHashParam("context") limNS = data.LimitedToNamespace
data.sort((a, b) => (getCleanClusterName(a.Name) > getCleanClusterName(b.Name)) - (getCleanClusterName(a.Name) < getCleanClusterName(b.Name))) if (limNS) {
fillClusterList(data, context); $("#limitNamespace").show().find("span").text(limNS)
}
initView(); // can only do it after loading cluster list fillClusters(limNS)
$.getJSON("/api/kube/namespaces").fail(function (xhr) {
reportError("Failed to get namespaces", xhr)
}).done(function (res) {
const ns = res.items.map(i => i.metadata.name)
$.each(ns, function (i, item) {
$("#upgradeModal #ns-datalist").append($("<option>", {
value: item,
text: item
}))
}) })
})
})
$.getJSON("/api/scanners").fail(function (xhr) { $.getJSON("/api/scanners").fail(function (xhr) {
reportError("Failed to get list of scanners", xhr) reportError("Failed to get list of scanners", xhr)
@@ -38,16 +21,49 @@ $(function () {
} }
} }
}) })
})
$.getJSON("/status").fail(function (xhr) { function fillClusters(limNS) {
reportError("Failed to get tool version", xhr) const clusterSelect = $("#cluster");
clusterSelect.change(function () {
window.location.href = "/#context=" + clusterSelect.find("input:radio:checked").val()
window.location.reload()
})
const namespaceSelect = $("#namespace");
namespaceSelect.change(function () {
let filteredNamespaces = []
namespaceSelect.find("input:checkbox:checked").each(function () {
filteredNamespaces.push($(this).val());
})
setFilteredNamespaces(filteredNamespaces)
filterInstalledList($("#installedList .body .row"))
})
$.getJSON("/api/kube/contexts").fail(function (xhr) {
reportError("Failed to get list of clusters", xhr)
}).done(function (data) { }).done(function (data) {
fillToolVersion(data) $("body").data("contexts", data)
if (data.LimitedToNamespace) { const context = getHashParam("context")
$("#limitNamespace").show().find("span").text(data.LimitedToNamespace) data.sort((a, b) => (getCleanClusterName(a.Name) > getCleanClusterName(b.Name)) - (getCleanClusterName(a.Name) < getCleanClusterName(b.Name)))
fillClusterList(data, context);
$.getJSON("/api/kube/namespaces").fail(function (xhr) {
reportError("Failed to get namespaces", xhr)
}).done(function (res) {
const ns = res.items.map(i => i.metadata.name)
$.each(ns, function (i, item) {
$("#upgradeModal #ns-datalist").append($("<option>", {
value: item,
text: item
}))
})
if (!limNS) {
fillNamespaceList(res.items)
} }
initView(); // can only do it after loading cluster and namespace lists
}) })
}) })
}
function initView() { function initView() {
$(".section").hide() $(".section").hide()
@@ -77,8 +93,10 @@ $("#topNav ul a").click(function () {
$("#topNav ul a").removeClass("active") $("#topNav ul a").removeClass("active")
const ctx = getHashParam("context") const ctx = getHashParam("context")
const filteredNamespace = getHashParam("filteredNamespace")
setHashParam(null, null) setHashParam(null, null)
setHashParam("context", ctx) setHashParam("context", ctx)
setHashParam("filteredNamespace", filteredNamespace)
if (self.hasClass("section-repo")) { if (self.hasClass("section-repo")) {
setHashParam("section", "repository") setHashParam("section", "repository")
@@ -171,10 +189,9 @@ function fillClusterList(data, context) {
opt.attr('title', elm.Name) opt.attr('title', elm.Name)
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(label)
if (elm.IsCurrent && !context) { const isCurrent = elm.IsCurrent && !context;
opt.find("input").prop("checked", true) const isSelected = context && elm.Name === context
setCurrentContext(elm.Name) if (isCurrent || isSelected) {
} else if (context && elm.Name === context) {
opt.find("input").prop("checked", true) opt.find("input").prop("checked", true)
setCurrentContext(elm.Name) setCurrentContext(elm.Name)
} }
@@ -182,6 +199,34 @@ function fillClusterList(data, context) {
}) })
} }
function fillNamespaceList(data) {
const curContextNamespaces = $("body").data("contexts").filter(obj => {
return obj.IsCurrent
})
console.log(curContextNamespaces)
if (!data || !data.length) {
$("#namespace").append("default")
return
}
Array.from(data).forEach(function (elm) {
const filteredNamespace = getHashParam("filteredNamespace")
let opt = $('<li><label><input type="checkbox" name="namespace" class="me-2"/><span></span></label></li>');
opt.attr('title', elm.metadata.name)
opt.find("input").val(elm.metadata.name).text(elm.metadata.name)
opt.find("span").text(elm.metadata.name)
if (filteredNamespace) {
if (filteredNamespace.split('+').includes(elm.metadata.name)) {
opt.find("input").prop("checked", true)
}
} else if (curContextNamespaces && curContextNamespaces[0].Namespace === elm.metadata.name) {
opt.find("input").prop("checked", true)
setFilteredNamespaces([elm.metadata.name])
}
$("#namespace").append(opt)
})
}
function setCurrentContext(ctx) { function setCurrentContext(ctx) {
setHashParam("context", ctx) setHashParam("context", ctx)
$.ajaxSetup({ $.ajaxSetup({
@@ -259,3 +304,42 @@ $("#cacheClear").click(function () {
window.location.reload() window.location.reload()
}) })
}) })
function showHideInstalledRelease(card, filteredNamespaces, filterStr) {
let releaseNamespace = card.data("namespace")
let releaseName = card.data("name")
let chartName = card.data("chart")
const shownByNS = !filteredNamespaces || filteredNamespaces.split('+').includes(releaseNamespace);
const shownByStr = releaseName.indexOf(filterStr) >= 0 || chartName.indexOf(filterStr) >= 0
if (shownByNS && shownByStr) {
card.show()
return true
} else {
card.hide()
return false
}
}
function filterInstalledList(list) {
const warnMsg = $("#installedList .all-filtered").hide();
let filterStr = $("#installedSearch").val().toLowerCase();
let filteredNamespaces = getHashParam("filteredNamespace")
let anyShown = false;
list.each(function (ix, card) {
anyShown |= showHideInstalledRelease($(card), filteredNamespaces, filterStr)
})
if (list.length && !anyShown) {
warnMsg.show()
}
}
function setFilteredNamespaces(filteredNamespaces) {
if (filteredNamespaces.length === 0 && getHashParam("filteredNamespace")) {
setHashParam("filteredNamespace")
} else if (filteredNamespaces.length !== 0) {
setHashParam("filteredNamespace", filteredNamespaces.join('+'))
}
}

View File

@@ -103,6 +103,13 @@ body > .container-fluid {
margin-bottom: 0.75rem !important; margin-bottom: 0.75rem !important;
} }
#installedList .header .spaced-out {
display: flex;
justify-content: space-between;
align-items: center;
padding-right: 0.5rem;
}
#installedList h2 { #installedList h2 {
font-family: Inter, serif; font-family: Inter, serif;
font-weight: 700; font-weight: 700;