mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
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:
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -33,16 +35,16 @@ function buildChartCard(elm) {
|
|||||||
$.getJSON("/api/helm/repo/search?name=" + chartName).fail(function (xhr) {
|
$.getJSON("/api/helm/repo/search?name=" + chartName).fail(function (xhr) {
|
||||||
reportError("Failed to get repo name for charts", xhr)
|
reportError("Failed to get repo name for charts", xhr)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
if(data.length > 0) {
|
if (data.length > 0) {
|
||||||
$.getJSON("/api/helm/charts/show?name=" + data[0].name).fail(function (xhr) {
|
$.getJSON("/api/helm/charts/show?name=" + data[0].name).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) {
|
||||||
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 + ")")
|
||||||
}
|
}
|
||||||
if(res.description) {
|
if (res.description) {
|
||||||
card.find(".rel-name div").text(res.description)
|
card.find(".rel-name div").text(res.description)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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"))
|
||||||
|
})
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,33 +1,16 @@
|
|||||||
$(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)
|
||||||
}).done(function (data) {
|
}).done(function (data) {
|
||||||
@@ -38,17 +21,50 @@ $(function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
$.getJSON("/status").fail(function (xhr) {
|
|
||||||
reportError("Failed to get tool version", xhr)
|
|
||||||
}).done(function (data) {
|
|
||||||
fillToolVersion(data)
|
|
||||||
if (data.LimitedToNamespace) {
|
|
||||||
$("#limitNamespace").show().find("span").text(data.LimitedToNamespace)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function fillClusters(limNS) {
|
||||||
|
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) {
|
||||||
|
$("body").data("contexts", data)
|
||||||
|
const context = getHashParam("context")
|
||||||
|
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('+'))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user