+
-
{{ value }}
+
{{ error }}
{{ t('imageScanner.general.none') }}
diff --git a/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistorySinceCell.vue b/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistorySinceCell.vue
new file mode 100644
index 0000000..2e44102
--- /dev/null
+++ b/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistorySinceCell.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistoryStatusCell.vue b/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistoryStatusCell.vue
new file mode 100644
index 0000000..272046a
--- /dev/null
+++ b/pkg/sbombastic-image-vulnerability-scanner/formatters/ScanHistoryStatusCell.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml
index 8b25a18..d46e226 100644
--- a/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml
+++ b/pkg/sbombastic-image-vulnerability-scanner/l10n/en-us.yaml
@@ -123,13 +123,11 @@ imageScanner:
cve:
allCves: All identified CVEs
affectingCvesOnly: Affecting CVEs only
- imageList:
- image: Image
- severity: severity
- repository: repository
- registry: Registry
buttons:
downloadCustomReport: Download custom report
+ downloadSbom: Download SBOM
+ downloadCsv: Image detail report (CSV)
+ downloadJson: Vulnerability report (JSON)
listTable:
checkbox:
groupByRepo: Group by repository
@@ -139,6 +137,14 @@ imageScanner:
repository: Repository
registry: Registry
vulnerabilities: vulnerabilities
+ filters:
+ placeholder:
+ image: Search by name
+ label:
+ image: Image
+ severity: Severity
+ repository: Repository
+ registry: Registry
imageDetails:
status: Status
vulnerabilities: Vulnerabilities
diff --git a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue
index 0b9fe11..3432fcd 100644
--- a/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue
+++ b/pkg/sbombastic-image-vulnerability-scanner/pages/c/_cluster/sbombastic-image-vulnerability-scanner/ImageOverview.vue
@@ -1,6 +1,6 @@
-
@@ -34,22 +36,64 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -82,12 +126,15 @@
:rows="row.images"
:headers="REPO_BASED_IMAGE_LIST_TABLE"
:search="false"
- :row-actions="false"
+ :row-actions="true"
:table-actions="false"
/>
+
+
+
@@ -99,11 +146,14 @@
import LabeledSelect from "@shell/components/form/LabeledSelect";
import DownloadCustomReport from "@pkg/components/common/DownloadCustomReport";
import TopRiskyImagesChart from "@pkg/components/TopRiskyImagesChart";
- import ImageRiskAssessment from "@pkg/components/ImageRiskAssessment"
- import { images } from "@pkg/data/sbombastic.rancher.io.image";
+ import ImageRiskAssessment from "@pkg/components/ImageRiskAssessment";
+ import ActionMenu from '@shell/components/ActionMenuShell.vue';
import { IMAGE_LIST_TABLE, REPO_BASED_TABLE, REPO_BASED_IMAGE_LIST_TABLE } from "@pkg/config/table-headers";
import { Checkbox } from '@components/Form/Checkbox';
-import { filter } from "lodash";
+ import { RESOURCE } from "@pkg/types";
+ import { saveAs } from 'file-saver';
+
+
export default {
name: 'imageOverview',
components: {
@@ -113,6 +163,7 @@ import { filter } from "lodash";
SortableTable,
DownloadCustomReport,
Checkbox,
+ ActionMenu
},
data() {
const filterCveOptions = [
@@ -140,7 +191,7 @@ import { filter } from "lodash";
}
];
return {
- rows: images,
+ rows: [],
REPO_BASED_TABLE: REPO_BASED_TABLE,
IMAGE_LIST_TABLE: IMAGE_LIST_TABLE,
REPO_BASED_IMAGE_LIST_TABLE: REPO_BASED_IMAGE_LIST_TABLE,
@@ -152,20 +203,67 @@ import { filter } from "lodash";
filterImageOptions,
selectedCveFilter: filterCveOptions[0],
selectedImageFilter: filterImageOptions[0],
- selectedImage: "",
- selectedSeverity: "",
- selectedRepository: "Any",
- selectedRegistry: "Any",
+ filters: {
+ imageSearch: '',
+ severitySearch: 'any',
+ repositorySearch: 'any',
+ registrySearch: 'any',
+ },
+ }
+ },
+ computed: {
+ customActions() {
+ const downloadSbom = {
+ action: 'downloadSbom',
+ label: this.t('imageScanner.images.buttons.downloadSbom'),
+ icon: 'icon-download',
+ enabled: true,
+ bulkable: false,
+ invoke: (_, res) => {
+ this.downloadSbom(res);
+ }
+ };
+ const downloadCsv = {
+ action: 'downloadCsv',
+ label: this.t('imageScanner.images.buttons.downloadCsv'),
+ icon: 'icon-download',
+ enabled: true,
+ bulkable: false,
+ invoke: async ({}, res = []) => {}
+ };
+ const downloadJson = {
+ action: 'downloadJson',
+ label: this.t('imageScanner.images.buttons.downloadJson'),
+ icon: 'icon-download',
+ enabled: true,
+ bulkable: false,
+ invoke: async ({}, res = []) => {}
+ };
+ return [
+ downloadSbom,
+ {divider: true},
+ downloadCsv,
+ downloadJson
+ ];
}
},
-
async fetch() {
- this.preprocessedDataset = this.preprocessData(this.rows);
+ await this.$store.dispatch('cluster/findAll', { type: RESOURCE.VULNERABILITY_REPORT });
+ const vulReportCRD = this.$store.getters['cluster/all']?.(RESOURCE.VULNERABILITY_REPORT);
+ this.preprocessData(vulReportCRD);
+ this.preprocessedDataset = this.preprocessData(vulReportCRD);
this.preprocessedImagesBak = _.cloneDeep(this.preprocessedDataset.preprocessedImages);
console.log("this.preprocessedDataset", this.preprocessedDataset)
},
-
methods: {
+ async downloadSbom(res) {
+ const target = (res && res.length ? res[0] : null);
+ console.log("target", target);
+ const sbom = await this.$store.dispatch('cluster/find', { type: RESOURCE.SBOM, id: target.id });
+ const spdxString = JSON.stringify(sbom.spdx, null, 2);
+ const sbomBlob = new Blob([spdxString], { type: 'application/json;charset=utf-8' });
+ saveAs(sbomBlob, `${sbom.metadata.name}-sbom.spdx.json`);
+ },
applyFilters() {
let filtered = _.cloneDeep(this.preprocessedImagesBak);
if (this.selectedImageFilter.value === "excludeBaseImages" || this.selectedImageFilter === "excludeBaseImages") {
@@ -197,18 +295,28 @@ import { filter } from "lodash";
onSelectionChange(selected) {
this.selectedRows = selected || [];
},
- preprocessData(images) {
- console.log("images", images);
- const severityKeys = ['critical', 'high', 'medium', 'low', 'none'];
- const chartData = {};
+ preprocessData(vulReportCRD) {
+ this.rows = [];
+ vulReportCRD.forEach(report => {
+ this.rows.push({
+ id: report.id,
+ metadata: {
+ name: `${report.imageMetadata.registry}:${report.imageMetadata.tag}`
+ },
+ spec: {
+ isBaseImage: true,
+ hasAffectedPackages: false,
+ repository: report.imageMetadata.repository,
+ registry: report.imageMetadata.registryURI,
+ scanResult: report.report.summary,
+ }
+ });
+ });
+ const severityKeys = ['critical', 'high', 'medium', 'low', 'unknown'];
const repoMap = new Map();
const preprocessedImages = [];
- for (const key of severityKeys) {
- chartData[key] = 0;
- }
-
- const topRiskyImages = images.map(image => {
+ this.rows.forEach(image => {
let repoRec = {};
let imageSeverity = "";
const mapKey = `${image.spec.repository},${image.spec.registry}`;
@@ -246,9 +354,6 @@ import { filter } from "lodash";
}
repoMap.set(mapKey, repoRec);
}
- for (const key of severityKeys) {
- chartData[key] += imageSeverity === key ? 1 : 0;
- }
preprocessedImages.push({
...image,
severity: imageSeverity || "none",
@@ -257,17 +362,9 @@ import { filter } from "lodash";
imageName: image.metadata.name,
cveCnt: image.spec.scanResult,
}
- }).sort((a, b) => {
- for (const key of severityKeys) {
- const diff = (b.cveCnt[key] || 0) - (a.cveCnt[key] || 0);
- if (diff !== 0) return diff;
- }
- return 0;
- }).slice(0, 5);
+ });
return {
preprocessedImages,
- topRiskyImages,
- chartData,
rowsByRepo: Array.from(repoMap.values())
};
},
@@ -275,9 +372,6 @@ import { filter } from "lodash";
console.log("isGrouped", this.isGrouped);
},
},
-
- computed: {
- },
}
@@ -352,4 +446,82 @@ import { filter } from "lodash";
}
}
+ .filter-row {
+ display: flex;
+ gap: 24px;
+ margin-bottom: 20px;
+ }
+
+ .filter-item {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ flex: 1;
+ }
+
+ .filter-item label {
+ font-weight: 400;
+ color: #6C6C76;
+ font-size: 14px;
+ }
+
+ .filter-input-wrapper {
+ display: flex;
+ align-items: center;
+ border: 1px solid #DCDEE7;
+ border-radius: 6px;
+ padding: 0 12px;
+ background: #FFF;
+ }
+
+ .filter-input {
+ flex: 1;
+ padding: 10px 0;
+ border: none;
+ outline: none;
+ font-size: 14px;
+ line-height: 19px;
+ background: transparent;
+ }
+
+ .score-input {
+ color: #BEC1D2;
+ }
+
+ .score-input::placeholder {
+ color: #BEC1D2;
+ }
+
+ .filter-input:focus {
+ outline: none;
+ }
+
+ .filter-input-wrapper:focus-within {
+ border-color: #007cba;
+ box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.1);
+ }
+
+ .filter-select {
+ padding: 10px 14px;
+ border: 1px solid #DCDEE7;
+ border-radius: 6px;
+ font-size: 14px;
+ background: #FFF;
+ outline: none;
+ }
+
+ .filter-select:focus {
+ border-color: #007cba;
+ box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.1);
+ }
+
+ .filter-actions {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 8px;
+ padding-top: 16px;
+ border-top: 1px solid #DCDEE7;
+ }
+
\ No newline at end of file
diff --git a/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts b/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts
index 04500a7..d4a1e68 100644
--- a/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts
+++ b/pkg/sbombastic-image-vulnerability-scanner/types/sbombastic-image-vulnerability-scanner.ts
@@ -3,12 +3,12 @@ export const SBOMBASTIC = {
CONTROLLER: "sbombastic",
SERVICE: "sbombastic-service",
SCHEMA: "sbombastic.rancher.io.registry",
-}
+};
export const SBOMBASTIC_REPOS = {
- CHARTS: 'https://charts.sbombastic.io',
- CHARTS_REPO: 'https://github.com/sbombastic/helm-charts',
- CHARTS_REPO_GIT: 'https://github.com/sbombastic/helm-charts.git',
- CHARTS_REPO_NAME: 'sbombastic-charts',
+ CHARTS: "https://charts.sbombastic.io",
+ CHARTS_REPO: "https://github.com/sbombastic/helm-charts",
+ CHARTS_REPO_GIT: "https://github.com/sbombastic/helm-charts.git",
+ CHARTS_REPO_NAME: "sbombastic-charts",
};
export const RESOURCE = {
REGISTRY: "sbombastic.rancher.io.registry",
@@ -31,4 +31,4 @@ export interface MetadataProperty {
label?: string;
value?: string;
tags?: string[];
-}
\ No newline at end of file
+}
diff --git a/yarn.lock b/yarn.lock
index 2aca8e0..b4a557e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8319,6 +8319,11 @@ file-saver@2.0.2:
resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==
+file-saver@^2.0.5:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.5.tgz#d61cfe2ce059f414d899e9dd6d4107ee25670c38"
+ integrity sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
+
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"