improvements to how scanning works, and helmchart ui

This commit is contained in:
Evan Jarrett
2026-04-29 10:12:25 -05:00
parent f9ba8ff62b
commit 9af6eccc9d
37 changed files with 1610 additions and 163 deletions
+75 -11
View File
@@ -438,6 +438,11 @@ async function deleteUntaggedManifests(repository) {
const confirmBtn = document.getElementById('confirm-untagged-delete-btn');
const originalText = confirmBtn.textContent;
const restoreButton = () => {
confirmBtn.disabled = false;
confirmBtn.textContent = originalText;
};
try {
confirmBtn.disabled = true;
confirmBtn.textContent = 'Deleting...';
@@ -449,26 +454,40 @@ async function deleteUntaggedManifests(repository) {
body: JSON.stringify({ repo: repository }),
});
const data = await response.json();
const raw = await response.text();
let data = null;
try {
data = raw ? JSON.parse(raw) : null;
} catch (_) {
// Non-JSON body (e.g., upstream proxy error page) — fall through.
}
if (response.ok) {
const deleted = (data && data.deleted) || 0;
const failed = (data && data.failed) || 0;
closeDialog(document.getElementById('untagged-delete-modal'));
showToast(`Deleted ${data.deleted} untagged manifest(s)`, 'success');
if (data.deleted > 0) {
location.reload();
if (failed > 0) {
showToast(`Deleted ${deleted} of ${deleted + failed} untagged manifest(s); ${failed} failed`, 'error');
} else if (deleted > 0) {
showToast(`Deleted ${deleted} untagged manifest(s)`, 'success');
} else {
showToast('No untagged manifests to delete', 'info');
}
confirmBtn.disabled = false;
confirmBtn.textContent = originalText;
if (deleted > 0) {
location.reload();
return;
}
restoreButton();
} else {
showToast(`Failed to delete untagged manifests: ${data.error || 'Unknown error'}`, 'error');
confirmBtn.disabled = false;
confirmBtn.textContent = originalText;
const errMsg = (data && data.error) || raw || `HTTP ${response.status}`;
const partial = data && data.deleted ? ` (${data.deleted} succeeded before failure)` : '';
showToast(`Failed to delete untagged manifests: ${errMsg}${partial}`, 'error');
restoreButton();
}
} catch (err) {
console.error('Error deleting untagged manifests:', err);
showToast(`Error: ${err.message}`, 'error');
confirmBtn.disabled = false;
confirmBtn.textContent = originalText;
restoreButton();
}
}
@@ -613,6 +632,51 @@ document.addEventListener('DOMContentLoaded', () => {
if (sel) sel.addEventListener('change', () => updatePullCommand(sel.value));
});
// Helm install/pull switcher. Persisted via localStorage (no profile setting
// — too minor to round-trip through the API). Uses event delegation +
// htmx:afterSettle so it survives the repo page's HTMX tag swaps; binding
// on DOMContentLoaded alone would only catch the first render.
function helmCmdSwitcher_render(mode) {
const container = document.getElementById('helm-cmd-container');
if (!container) return;
const registryURL = container.dataset.registryUrl;
const ownerHandle = container.dataset.ownerHandle;
const repoName = container.dataset.repoName;
const tag = container.dataset.tag || '';
const ociRef = 'oci://' + registryURL + '/' + ownerHandle + '/' + repoName;
const versionFlag = tag ? ' --version ' + tag : '';
const cmd = mode === 'pull'
? 'helm pull ' + ociRef + versionFlag
: 'helm install ' + repoName + ' ' + ociRef + versionFlag;
const display = document.getElementById('helm-cmd-display');
if (!display) return;
const code = display.querySelector('code');
if (code) code.textContent = cmd;
const btn = display.querySelector('[data-cmd]');
if (btn) btn.dataset.cmd = cmd;
}
function helmCmdSwitcher_apply() {
const sel = document.getElementById('helm-cmd-switcher');
if (!sel) return;
const saved = lsGet('helm-cmd');
if (saved === 'install' || saved === 'pull') {
sel.value = saved;
}
helmCmdSwitcher_render(sel.value);
}
document.addEventListener('DOMContentLoaded', helmCmdSwitcher_apply);
document.body.addEventListener('htmx:afterSettle', helmCmdSwitcher_apply);
// Delegated change handler: works on the initial render AND on any switcher
// HTMX swaps into the DOM later (repo page tag switch).
document.addEventListener('change', (e) => {
if (!e.target || e.target.id !== 'helm-cmd-switcher') return;
lsSet('helm-cmd', e.target.value);
helmCmdSwitcher_render(e.target.value);
});
// Install page: platform tab switcher. Each .platform-tab has data-platform
// pointing at a sibling panel (#<platform>-content). No-op off the install page.
document.addEventListener('DOMContentLoaded', () => {
+4 -2
View File
@@ -202,7 +202,7 @@ document.body.addEventListener('htmx:beforeSwap', () => {
function initTabController() {
if (!document.getElementById('tag-content')) return;
const validTabs = ['overview', 'layers', 'vulns', 'sbom', 'artifacts'];
const validTabs = ['overview', 'layers', 'vulns', 'sbom', 'artifacts', 'chart'];
// State per target id: 'loading' while a request is in-flight, 'loaded'
// on success. On error we clear the entry so the retry button can
// trigger a fresh fetch; without a separate 'loading' marker, a failing
@@ -328,6 +328,7 @@ function initTabController() {
if (tabId === 'layers') { const u = contentUrl('layers'); if (u) lazyLoad('layers-content', u); }
if (tabId === 'vulns') { const u = contentUrl('vulns'); if (u) lazyLoad('vulns-content', u); }
if (tabId === 'sbom') { const u = contentUrl('sbom'); if (u) lazyLoad('sbom-content', u); }
if (tabId === 'chart') { const u = contentUrl('chart'); if (u) lazyLoad('chart-content', u); }
};
function initTabs() {
@@ -338,6 +339,7 @@ function initTabController() {
['layers-tab-btn', 'layers-content', () => contentUrl('layers')],
['vulns-tab-btn', 'vulns-content', () => contentUrl('vulns')],
['sbom-tab-btn', 'sbom-content', () => contentUrl('sbom')],
['chart-tab-btn', 'chart-content', () => contentUrl('chart')],
];
prefetch.forEach(([btnId, targetId, urlFn]) => {
const btn = document.getElementById(btnId);
@@ -386,7 +388,7 @@ function initTabController() {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' ||
e.target.tagName === 'SELECT' || e.target.isContentEditable) return;
if (e.ctrlKey || e.metaKey || e.altKey) return;
const map = { o: 'overview', l: 'layers', v: 'vulns', s: 'sbom', a: 'artifacts' };
const map = { o: 'overview', l: 'layers', v: 'vulns', s: 'sbom', a: 'artifacts', c: 'chart' };
const tab = map[e.key.toLowerCase()];
if (tab && validTabs.indexOf(tab) !== -1) window.switchRepoTab(tab);
});