diff --git a/internal/output/html.go b/internal/output/html.go index e848a2cc626..09ffe725517 100644 --- a/internal/output/html.go +++ b/internal/output/html.go @@ -35,6 +35,18 @@ func formatRating(rating severity.Rating) string { return strings.ToLower(string(rating)) } +type VulnTableEntryArgument struct { + Element VulnResult + IsHidden bool +} + +func buildVulnTableEntryArgument(element VulnResult, isHidden bool) VulnTableEntryArgument { + return VulnTableEntryArgument{ + IsHidden: isHidden, + Element: element, + } +} + func PrintHTMLResults(vulnResult *models.VulnerabilityResults, outputWriter io.Writer) error { // htmlResult := BuildHTMLResults(vulnResult) result := BuildResults(vulnResult) @@ -48,10 +60,11 @@ func PrintHTMLResults(vulnResult *models.VulnerabilityResults, outputWriter io.W "add": func(a, b int) int { return a + b }, - "getFilteredVulnReasons": getFilteredVulnReasons, - "getBaseImageName": getBaseImageName, - "formatSlice": formatSlice, - "formatLayerCommand": formatLayerCommand, + "getFilteredVulnReasons": getFilteredVulnReasons, + "getBaseImageName": getBaseImageName, + "formatSlice": formatSlice, + "formatLayerCommand": formatLayerCommand, + "buildVulnTableEntryArgument": buildVulnTableEntryArgument, } tmpl := template.Must(template.New("").Funcs(funcMap).ParseFS(templates, TemplateDir)) diff --git a/internal/output/html/base_image_template.gohtml b/internal/output/html/base_image_template.gohtml index a3e2cf5c98a..60d92fa22bd 100644 --- a/internal/output/html/base_image_template.gohtml +++ b/internal/output/html/base_image_template.gohtml @@ -36,7 +36,7 @@ -
+
{{ range .AllLayers }} {{ $index := .Index }} {{ $originalCommand := .LayerMetadata.Command }} diff --git a/internal/output/html/filter_template.gohtml b/internal/output/html/filter_template.gohtml index 45568d4fc6c..f73b13c417c 100644 --- a/internal/output/html/filter_template.gohtml +++ b/internal/output/html/filter_template.gohtml @@ -9,7 +9,7 @@ keyboard_arrow_down
-
+

All layers ({{ .VulnCount.AnalysisCount.Regular }})

@@ -24,7 +24,7 @@ {{ $diffID := .LayerMetadata.DiffID }} {{ $longCommand := false }} {{ if gt (len $command) 109 }} - {{ $longCommand = true }} + {{ $longCommand = true }} {{ end }} {{ if gt .Count.AnalysisCount.Regular 0 }} @@ -34,7 +34,7 @@ {{ if $longCommand }} {{ $originalCommand }} {{ end }} -
+
{{ template "severity_summary_template.gohtml".Count.SeverityCount }}
{{ end }} @@ -54,28 +54,28 @@ keyboard_arrow_down -
+
{{ if .IsContainerScanning }} -
+
+ Default ({{ .VulnTypeSummary.All }}) + {{ end }} -
+
+ Project Vulnerabilities ({{ .VulnTypeSummary.Project }}) + {{ if .IsContainerScanning }} -
+
+ Operating system vulnerabilities ({{ .VulnTypeSummary.OS }}) + {{ end }} -
+
+ Uncalled/Unimportant ({{ .VulnTypeSummary.Hidden }}) +
diff --git a/internal/output/html/package_table_template.gohtml b/internal/output/html/package_table_template.gohtml index dc3dcc702be..80c05ab4919 100644 --- a/internal/output/html/package_table_template.gohtml +++ b/internal/output/html/package_table_template.gohtml @@ -30,19 +30,21 @@ {{ $element.Name }} {{ $element.InstalledVersion }} +
{{ if ne $element.VulnCount.FixableCount.UnFixed 0 }} - {{ if eq $element.VulnCount.FixableCount.Fixed 0}} -

No fix available

- {{ else }} -
-

Partial fixes available

- Upgrading the package will fix {{ $element.VulnCount.FixableCount.Fixed }} out of {{ - $element.VulnCount.AnalysisCount.Regular }} vulnerabilities -
- {{ end }} + {{ if eq $element.VulnCount.FixableCount.Fixed 0}} +

No fix available

+ All {{ $element.VulnCount.AnalysisCount.Regular }} vulnerabilities have no fixed version available. + {{ else }} +

Partial fixes available

+ Upgrading the package can fix {{ $element.VulnCount.FixableCount.Fixed }} out of {{ + $element.VulnCount.AnalysisCount.Regular }} vulnerabilities. + {{ end }} {{ else }} -

Fix available

+

Fix available

+ Upgrading the package can fix all {{ $element.VulnCount.FixableCount.Fixed }} vulnerabilities. {{ end }} +
{{ if ne $element.VulnCount.AnalysisCount.Regular 0 }} @@ -52,9 +54,9 @@ {{ end }} - + -
+
{{ if and $element.LayerDetail.LayerInfo.LayerMetadata (not (eq $element.LayerDetail.LayerInfo.LayerMetadata.Command "")) }} {{ $index := $element.LayerDetail.LayerIndex }} {{ $diffID := $element.LayerDetail.LayerInfo.LayerMetadata.DiffID }} @@ -82,7 +84,7 @@

Introduced in layer #{{ $index }}: 

{{ $command }}

- {{ $commandDetail }} + {{ $commandDetail }}
{{ else }}

Introduced in layer #{{ $index }}: 

diff --git a/internal/output/html/package_view_template.gohtml b/internal/output/html/package_view_template.gohtml index 9a4a23e3aee..1f83ab0a4bf 100644 --- a/internal/output/html/package_view_template.gohtml +++ b/internal/output/html/package_view_template.gohtml @@ -4,7 +4,7 @@
{{ range .Sources }}
-

Source: {{ .Name }}

+

Source: {{ .Name }}

{{ template "package_table_template.gohtml" .Packages }}
{{ end }} diff --git a/internal/output/html/report_template.gohtml b/internal/output/html/report_template.gohtml index 917faef7a84..19756b088cb 100644 --- a/internal/output/html/report_template.gohtml +++ b/internal/output/html/report_template.gohtml @@ -7,11 +7,12 @@ - {{ template "style.html" }} + - {{ template "script.html" }}
@@ -57,6 +58,9 @@
+ diff --git a/internal/output/html/script.html b/internal/output/html/script.html deleted file mode 100644 index e2bcdf5ede6..00000000000 --- a/internal/output/html/script.html +++ /dev/null @@ -1,535 +0,0 @@ - diff --git a/internal/output/html/script.js b/internal/output/html/script.js new file mode 100644 index 00000000000..3b14d23ca72 --- /dev/null +++ b/internal/output/html/script.js @@ -0,0 +1,488 @@ +const selectedTypeFilterValue = new Set(); +selectedTypeFilterValue.add("all"); +let selectedLayer = "all"; + +function quickFilterByLayer(DiffID, layerCommand) { + resetFilterText(); + applyFilters(selectedTypeFilterValue, DiffID); + const selectedDisplay = document.getElementById("layer-filter-selected"); + selectedDisplay.textContent = layerCommand; +} + +function showBaseImageLayer(imageID) { + const detailElementID = "base-image-details-" + imageID; + const detailsElement = document.getElementById(detailElementID); + + const icon = document.querySelector( + `#base-image-summary-${imageID} .material-icons` + ); // Select the icon within the row + + const hidBlock = detailsElement.classList.toggle("hide-block"); + icon.classList.toggle("expanded", !hidBlock); +} + +function showPackageDetails(detailsId) { + const detailsElement = document.getElementById( + "table-tr-" + detailsId + "-details" + ); + const icon = document.querySelector(`#table-tr-${detailsId} .material-icons`); // Select the icon within the row + + const hidBlock = detailsElement.classList.toggle("hide-block"); + icon.classList.toggle("expanded", !hidBlock); +} + +function openVulnInNewTab(inputString) { + const osvURL = `https://osv.dev/${inputString}`; + const tabs = document.getElementById("tabs"); + const tabSwitches = document.getElementById("tab-switch"); + + const existingTab = document.getElementById(inputString); + if (existingTab) { + openTab(inputString); + return; + } + + // Create the new tab div. + const newTab = document.createElement("div"); + newTab.id = inputString; // Set the ID to the input string + newTab.className = "tab osv-tab"; // Set the class name + + // Create the iframe element. + const iframe = document.createElement("iframe"); + iframe.src = osvURL; + + // Create a new tab button + const newTabButton = document.createElement("div"); + newTabButton.id = inputString + "-button"; + newTabButton.className = "tab-switch-button"; + newTabButton.onclick = function () { + openTab(inputString); + }; + + // Add

and elements to the button + const newTabTextContainer = document.createElement("div"); + newTabTextContainer.className = "tab-button-text-container"; + const newTabText = document.createElement("p"); + newTabText.textContent = inputString; + newTabTextContainer.appendChild(newTabText); + + const newTabButtonBorder = document.createElement("div"); + newTabButtonBorder.className = "tab-switch-button-border"; + newTabTextContainer.appendChild(newTabButtonBorder); + + newTabButton.appendChild(newTabTextContainer); + + const closeIcon = document.createElement("span"); + closeIcon.className = "material-icons"; + closeIcon.textContent = "close"; + // Add the onclick function to the close icon + closeIcon.onclick = function (event) { + event.stopPropagation(); // Prevent the click from opening the tab + closeVulnTab(inputString); + }; + + newTabButton.appendChild(closeIcon); + + // Add the iframe to the new tab div. + newTab.appendChild(iframe); + // Add the iframe to the container. + tabs.appendChild(newTab); + tabSwitches.appendChild(newTabButton); + + openTab(newTab.id); +} + +function closeVulnTab(inputString) { + const tabToRemove = document.getElementById(inputString); + const buttonToRemove = document.getElementById(inputString + "-button"); + const tabs = document.getElementById("tabs"); + const tabSwitches = document.getElementById("tab-switch"); + + if (tabToRemove && buttonToRemove) { + const nextTabButton = + buttonToRemove.nextElementSibling || + buttonToRemove.previousElementSibling; + + tabs.removeChild(tabToRemove); + tabSwitches.removeChild(buttonToRemove); + + if (nextTabButton) { + const nextTabId = nextTabButton.id.replace("-button", ""); + openTab(nextTabId); + } + } +} + +function openTab(activeTabId) { + const tabs = document.getElementsByClassName("tab"); + const tabButtons = document.getElementsByClassName("tab-switch-button"); + for (let i = 0; i < tabs.length; i += 1) { + tabs[i].classList.toggle("hide-block", tabs[i].id !== activeTabId); + tabButtons[i].classList.toggle( + "tab-switch-button-selected", + tabs[i].id === activeTabId + ); + } +} + +function hideAllFilterOptions() { + const containers = document.getElementsByClassName("filter-option-container"); + + for (const container of containers) { + container.classList.toggle("hide-block", true); + } +} + +function toggleFilter(input) { + const targetID = input + "-filter-option-container"; + let optionContainer = document.getElementById(targetID); + const containers = document.getElementsByClassName("filter-option-container"); + for (const loopContainer of containers) { + if (loopContainer.id === targetID) { + optionContainer.classList.toggle("hide-block"); + } else { + loopContainer.classList.toggle("hide-block", true); + } + } +} + +function showAndHideParentSections() { + const ecosystemContainers = document.querySelectorAll(".ecosystem-container"); + + ecosystemContainers.forEach(ecosystemContainer => { + const sourceContainers = + ecosystemContainer.querySelectorAll(".source-container"); + let ecosystemHasVisibleSources = false; + + sourceContainers.forEach(sourceContainer => { + const packageRows = sourceContainer.querySelectorAll(".package-tr"); + let sourceHasVisibleRows = false; + + packageRows.forEach(packageRow => { + let packageDetails = document.getElementById( + packageRow.id + "-details" + ); + const vulnRows = packageDetails.querySelectorAll(".vuln-tr"); + let packageHasVisibleRows = false; + vulnRows.forEach(vulnRow => { + if (!vulnRow.classList.contains("hide-block")) { + packageHasVisibleRows = true; + return; + } + }); + if (packageHasVisibleRows) { + sourceHasVisibleRows = true; + packageRow.classList.toggle("hide-block", false); + return; + } else { + packageRow.classList.toggle("hide-block", true); + packageDetails.classList.toggle("hide-block", true); + const icon = document.querySelector( + `#${packageRow.id} .material-icons` + ); + icon.classList.remove("expanded"); // Rotate back to 0 degrees + } + }); + + sourceContainer.classList.toggle("hide-block", !sourceHasVisibleRows); + + if (sourceHasVisibleRows) { + ecosystemHasVisibleSources = true; + return; + } + }); + + ecosystemContainer.classList.toggle( + "hide-block", + !ecosystemHasVisibleSources + ); + }); +} + +function showAllVulns() { + const vulnRows = document.getElementsByClassName("vuln-tr"); + for (const row of vulnRows) { + let isUncalled = row.classList.contains("uncalled-tr"); + row.classList.toggle("hide-block", isUncalled); + } + + showAndHideParentSections(); +} + +function applyFilters(selectedTypeFilterValue, selectedLayerFilterValue) { + // Show all vulnerabilities and then hide those that do not match the filter requirements. + showAllVulns(); + applyTypeFilter(selectedTypeFilterValue); + applyLayerFilter(selectedLayerFilterValue); + showAndHideParentSections(); +} + +function applyTypeFilter(selectedValue) { + updateTypeFilterText(selectedValue); + let selectedAll = selectedValue.has("all"); + let selectedProject = selectedValue.has("project"); + let selectedOS = selectedValue.has("os"); + let selectedUncalled = selectedValue.has("uncalled"); + if (selectedAll) { + selectedProject = true; + selectedOS = true; + } + + const ecosystemElements = document.querySelectorAll(".ecosystem-container"); + + ecosystemElements.forEach(ecosystemElement => { + const vulnElements = ecosystemElement.querySelectorAll(".vuln-tr"); + vulnElements.forEach(vuln => { + if (vuln.classList.contains("uncalled-tr")) { + vuln.classList.toggle("hide-block", !selectedUncalled); + } + if ( + (ecosystemElement.classList.contains("os-type") && !selectedOS) || + (ecosystemElement.classList.contains("project-type") && + !selectedProject) + ) { + vuln.classList.toggle("hide-block", true); + } + }); + }); +} + +function applyLayerFilter(selectedLayerID) { + const tableRows = document.querySelectorAll("tr.has-layer-info"); + tableRows.forEach(row => { + const rowLayerID = row.getAttribute("data-layer"); + if (selectedLayerID !== "all" && rowLayerID !== selectedLayerID) { + const packageDetails = document.getElementById(row.id + "-details"); + const vulnElements = packageDetails.querySelectorAll(".vuln-tr"); + vulnElements.forEach(vuln => { + vuln.classList.toggle("hide-block", true); + }); + } + }); +} + +function updateTypeFilterText(_selectedValue) { + const typeSelected = document.getElementById("type-filter-selected"); + const selectedVulnCount = document.getElementById("selected-count"); + + const allTypeCheckbox = document.getElementById("all-type-checkbox"); + const osTypeCheckbox = document.getElementById("os-type-checkbox"); + const projectTypeCheckbox = document.getElementById("project-type-checkbox"); + const uncalledTypeCheckbox = document.getElementById( + "uncalled-type-checkbox" + ); + + let selectedText = ""; + let selectedCount = 0; + + if (projectTypeCheckbox && projectTypeCheckbox.checked) { + selectedText += (selectedText ? ", " : "") + "Project"; + const projectTypeVulnCount = projectTypeCheckbox.getAttribute( + "data-type-project-count" + ); + selectedCount += parseInt(projectTypeVulnCount, 10); + } + if (osTypeCheckbox && osTypeCheckbox.checked) { + selectedText += (selectedText ? ", " : "") + "OS"; + const osTypeVulnCount = osTypeCheckbox.getAttribute("data-type-os-count"); + selectedCount += parseInt(osTypeVulnCount, 10); + } + if (uncalledTypeCheckbox && uncalledTypeCheckbox.checked) { + selectedText += (selectedText ? ", " : "") + "Unimportant"; + const uncalledTypeVulnCount = uncalledTypeCheckbox.getAttribute( + "data-type-uncalled-count" + ); + selectedCount += parseInt(uncalledTypeVulnCount, 10); + } + + if ( + allTypeCheckbox && + allTypeCheckbox.checked && + uncalledTypeCheckbox && + !uncalledTypeCheckbox.checked + ) { + selectedText = "Default"; + } + + typeSelected.textContent = selectedText; + selectedVulnCount.textContent = selectedCount; +} + +function resetFilterText() { + const layerSelected = document.getElementById("layer-filter-selected"); + const allLayerCheckedBox = document.getElementById("all-layer-checkbox"); + if (layerSelected) { + layerSelected.textContent = + "All layers (" + + allLayerCheckedBox.getAttribute("data-layer-all-count") + + ")"; + } + + const typeSelected = document.getElementById("type-filter-selected"); + const selectedVulnCount = document.getElementById("selected-count"); + const allTypeCheckedBox = document.getElementById("all-type-checkbox"); + const uncalledTypeCheckBox = document.getElementById( + "uncalled-type-checkbox" + ); + if (allTypeCheckedBox) { + typeSelected.textContent = "Default"; + selectedVulnCount.textContent = allTypeCheckedBox.getAttribute( + "data-type-all-count" + ); + allLayerCheckedBox.checked = true; + uncalledTypeCheckBox.checked = false; + } else { + const projectTypeCheckedBox = document.getElementById( + "project-type-checkbox" + ); + projectTypeCheckedBox.checked = true; + typeSelected.textContent = "Default"; + selectedVulnCount.textContent = projectTypeCheckedBox.getAttribute( + "data-type-project-count" + ); + uncalledTypeCheckBox.checked = false; + } +} + +function resetSearchText() { + const vulnSearchInput = document.getElementById("vuln-search"); + if (vulnSearchInput.value != "") { + vulnSearchInput.value = ""; + showAllVulns(); + } +} + +function resetTypeCheckbox() { + const allTypeCheckbox = document.getElementById("all-type-checkbox"); + const osTypeCheckbox = document.getElementById("os-type-checkbox"); + const projectTypeCheckbox = document.getElementById("project-type-checkbox"); + const uncalledTypeCheckbox = document.getElementById( + "uncalled-type-checkbox" + ); + + if (allTypeCheckbox) { + allTypeCheckbox.checked = true; + projectTypeCheckbox.checked = true; + if (osTypeCheckbox) { + osTypeCheckbox.checked = true; + } + uncalledTypeCheckbox.checked = false; + } +} + +document.addEventListener("DOMContentLoaded", function () { + resetFilterText(); + showAndHideParentSections(); + + // Implement filter for vulnerability types + const typeFilterOptions = document.getElementById( + "type-filter-option-container" + ); + + typeFilterOptions.addEventListener("change", function () { + resetSearchText(); + const changedElement = event.target; + const allTypesCheckbox = document.getElementById("all-type-checkbox"); + const projectCheckbox = document.getElementById("project-type-checkbox"); // Project vulnerabilities + const osCheckbox = document.getElementById("os-type-checkbox"); // OS vulnerabilities + const uncalledCheckbox = document.getElementById("uncalled-type-checkbox"); // OS vulnerabilities + selectedTypeFilterValue.clear(); + + if (allTypesCheckbox != null) { + if (changedElement == allTypesCheckbox) { + osCheckbox.checked = allTypesCheckbox.checked; + projectCheckbox.checked = allTypesCheckbox.checked; + if (allTypesCheckbox.checked === true) { + selectedTypeFilterValue.add("all"); + } + } + if (osCheckbox.checked === false || projectCheckbox.checked === false) { + allTypesCheckbox.checked = false; + } + + if (osCheckbox.checked) { + selectedTypeFilterValue.add("os"); + } + } + + if (projectCheckbox.checked) { + selectedTypeFilterValue.add("project"); + } + + if (uncalledCheckbox.checked) { + selectedTypeFilterValue.add("uncalled"); + } + + applyFilters(selectedTypeFilterValue, selectedLayer); + }); + + // Implement layer filter + const layerFilterOptionsContainer = document.getElementById( + "layer-filter-option-container" + ); + + if (layerFilterOptionsContainer) { + layerFilterOptionsContainer.addEventListener("click", event => { + const clickedOption = event.target.closest(".layer-filter-option"); + if (clickedOption) { + resetSearchText(); + selectedLayer = clickedOption.getAttribute("data-layer-hash"); + const selectedDisplay = document.getElementById( + "layer-filter-selected" + ); + const layerCommand = clickedOption.querySelector("p:first-child"); + selectedDisplay.textContent = layerCommand.textContent; + + hideAllFilterOptions(); + applyFilters(selectedTypeFilterValue, selectedLayer); + } + }); + } + + // Hide filter options when clicking other parts + const filterSections = document.querySelectorAll("div.filter"); + + document.addEventListener("click", event => { + if (![...filterSections].some(c => c.contains(event.target))) { + hideAllFilterOptions(); + } + }); + + // Search bar + const vulnSearchInput = document.getElementById("vuln-search"); + vulnSearchInput.addEventListener("keyup", event => { + resetFilterText(); + selectedTypeFilterValue.clear(); + selectedTypeFilterValue.add("all"); + selectedLayer = "all"; + resetTypeCheckbox(); + + const searchTerm = vulnSearchInput.value.trim().toLowerCase(); + + const vulnRows = document.querySelectorAll("[data-vuln-id]"); + + if (searchTerm === "") { + showAllVulns(); + return; + } + + vulnRows.forEach(row => { + const vulnID = row.getAttribute("data-vuln-id").toLowerCase(); + row.classList.toggle("hide-block", !vulnID.includes(searchTerm)); + }); + showAndHideParentSections(); + }); + + // Implement tooltips + document.querySelectorAll(".tooltip").forEach(elem => { + elem.addEventListener("mouseover", event => { + const rect = elem.getBoundingClientRect(); + const tooltipElem = elem.querySelector(".tooltiptext"); + + tooltipElem.style.left = rect.left + "px"; + tooltipElem.style.top = rect.top + "px"; + }); + }); + + showAllVulns(); +}); diff --git a/internal/output/html/style.css b/internal/output/html/style.css new file mode 100644 index 00000000000..14db2c16b47 --- /dev/null +++ b/internal/output/html/style.css @@ -0,0 +1,628 @@ +body { + margin: 0; + padding: 0; + background: #292929; + color: #fff; + overflow-y: scroll; + font-family: "Overpass Mono", monospace; + font-size: 12pt; + font-weight: 100; + min-width: fit-content; +} + +a { + color: inherit; +} + +.container { + max-width: 1400px; + margin: 50px auto; + padding: 20px; + border-radius: 4px; + box-shadow: 0 0 5px rgba(255, 255, 255, 0.15); + min-height: 90vh; +} + +h2 { + margin-top: 50px; + font-size: 15pt; +} + +h3 { + font-size: 12pt; + margin-top: 30px; +} + +h1, +h2, +h3 { + font-family: "Overpass Mono", monospace; + font-weight: normal; +} + +.ecosystem { + margin-top: 20px; +} + +header { + display: flex; + margin-bottom: 50px; + justify-content: space-between; +} + +#header-left { + display: flex; + align-items: center; +} + +.logo { + height: 20px; +} + +#header-left .vl { + border-left: 2px solid #fff; + height: 25px; + margin-left: 20px; + margin-right: 20px; +} + +#header-left h1 { + font-size: 23px; +} + +#header-right { + display: flex; + align-items: center; +} + +#header-right ::after { + display: inline-block; + content: " "; + background-image: url(https://osv.dev/static/img/external-link.svg); + width: 16px; + height: 16px; + margin-left: 3px; + vertical-align: middle; +} + +.material-icons { + vertical-align: middle; + transform: rotate(0deg); + transition: transform 0.2s ease; + user-select: none; +} + +.material-icons.expanded { + transform: rotate(90deg); +} + +.vuln-table { + width: 100%; + text-align: left; + margin-bottom: 40px; + margin-top: 10px; + color: rgba(255, 255, 255, 0.87); + border-spacing: 0; +} + +.vuln-table th { + border-bottom: 1px solid rgba(255, 255, 255, 0.33); + padding: 16px; +} + +.table-tr { + line-height: 30px; +} + +.table-tr td { + word-break: break-word; + width: 200px; + padding: 16px; +} + +.table-tr:hover { + background-color: rgba(255, 255, 255, 0.04); +} + +.table-tr-details > td { + border-bottom: 1px solid rgba(255, 255, 255, 0.2); +} + +.vuln-tr { + display: table-row; +} + +.vuln-id { + color: #d9534f; +} + +.package-details { + text-align: left; + padding: 20px 50px 30px 50px; + display: block; + max-height: 800px; + overflow: auto; +} + +.package-details p { + margin-bottom: 10px; + margin-top: 10px; +} + +.table-tr:hover .open-in-tab-tag { + display: inline; +} + +.open-in-tab-tag { + display: none; + border: 1px solid rgba(255, 255, 255, 0.12); + padding: 5px; + border-radius: 4px; + width: fit-content; +} + +.open-in-tab-cell { + width: 90px !important; + cursor: pointer; +} + +.severity-cell { + width: 60px !important; + user-select: none; +} + +.fixable-tag { + border-radius: 4px; + max-width: 250px; + width: fit-content; + padding: 0 5px; + white-space: nowrap; + overflow: hidden; + text-align: center; + user-select: none; +} + +.has-fix { + white-space: break-spaces; + background-color: #6a6a6a; +} + +.no-fix { + background-color: #252525; + border: 1px solid #3c4043; +} + +.hide-block + .table-tr-details { + /* If details is after a hidden block, also hide details */ + + display: none; +} + +.uncalled-text { + color: #808080; +} + +#vuln-tab { + display: block; + margin-top: 30px; +} + +#tab-switch { + text-align: left; + border-bottom: 1px solid #6c6c6c; + padding-left: 20px; + display: flex; + margin-bottom: 50px; +} + +.tab-switch-button { + width: 180px; + outline: 0; + cursor: pointer; + background-color: transparent; + text-align: center; + display: flex; +} + +.tab-button-text-container { + flex-direction: column; + align-items: center; +} + +.tab-button-text-container p { + width: 160px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-bottom: 2px solid transparent; + color: #5f6368; +} + +.tab-switch-button span { + cursor: pointer; + font-size: 16px; + display: none; +} + +.tab-switch-button-border { + width: 85%; + height: 3px; + background-color: transparent; + margin: auto; +} + +.tab-button-text-container:hover p { + color: #fff; +} + +.tab-switch-button-selected:hover span { + display: block; +} + +.tab-switch-button-selected .tab-switch-button-border { + background-color: #eee; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +.tab-switch-button-selected p { + color: #fff; +} + +.osv-tab iframe { + width: 100%; + min-height: 1000px; + border: none; + background-color: #000; +} + +#vuln-tab { + display: block; +} + +.package-tr { + cursor: pointer; +} + +.icon-td { + width: 10px !important; +} + +#filter-section { + display: flex; + width: 60%; +} + +.filter-container { + margin-right: 20px; + max-width: 600px; +} + +.filter { + margin-top: 10px; + max-width: 600px; + min-width: 250px; + background-color: transparent; + border-radius: 8px; + overflow: hidden; + color: #fff; + border: 1px solid #ddd; + display: flex; + justify-content: space-between; + padding: 0 20px; +} + +#layer-filter { + width: 400px !important; +} + +.filter-selected { + white-space: nowrap; + overflow: hidden; +} + +.filter-option-container { + border: 1px solid #ddd; + border-radius: 8px; + position: absolute; + background-color: #292929; + min-width: 200px; + max-width: 1400px; + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); + z-index: 1; + display: block; +} + +.filter-option { + border-bottom: 1px dotted #ddd; + padding: 15px 20px; + white-space: nowrap; + cursor: pointer; + display: block; +} + +.filter-option input, +.filter-option label { + cursor: pointer; +} + +#layer-filter-option-container { + width: 1400px; +} + +.layer-filter-option { + display: flex; + justify-content: space-between; +} + +.filter-option:hover { + background-color: rgba(255, 255, 255, 0.04); +} + +.filter-icon i { + position: relative; + top: 25%; +} + +#summary-section { + display: flex; + justify-content: space-between; + width: 100%; +} + +#severity-section { + overflow: hidden; +} + +.severity-long { + text-align: center; + min-width: 100px; + overflow: hidden; + white-space: nowrap; + user-select: none; +} + +.severity-long p { + font-size: 10pt; + padding: 5px 5px; + border-radius: 4px; + margin-right: 3px; +} + +.critical { + background-color: #ad0300; +} + +.high { + background-color: #ffa500; +} + +.medium { + background-color: #ffd700; + color: #292929; +} + +.low { + background-color: #53aa33; + color: #292929; +} + +.unknown { + background-color: #80868b; +} + +.severity-count-summary { + display: flex; +} + +.severity-short { + width: 40px; + user-select: none; +} + +.severity-short p { + text-align: center; +} + +.severity-short-first p { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} + +.severity-short-last p { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.severity-cell .severity-short p { + border-radius: 4px; +} + +/* CSS for the search box */ +.search-box { + display: flex; + align-items: center; + width: 100%; + border: 1px solid #fff; + background-color: transparent; + border-radius: 8px; + margin-top: 30px; +} + +.search-box input[type="text"] { + flex: 1 0 auto; + padding: 15px 10px; + + border: none; + background-color: transparent; + color: #fff; + font-size: 12pt; +} + +.search-box input[type="text"]:focus { + outline: none; +} + +.search-icon { + height: fit-content; + padding: 13px; +} + +.package-detail-title { + color: #9aa0a6; +} + +.layer-command-container { + display: flex; +} + +.inner-table { + width: 100%; + border: 0.5px solid rgba(255, 255, 255, 0.12); + border-spacing: 0px; + border-radius: 4px; +} + +.inner-table th { + border-bottom: 0.5px solid rgba(255, 255, 255, 0.12); +} + +.inner-table td { + border-bottom: 0.5px solid rgba(255, 255, 255, 0.12); +} + +.expand-icon i.rotated { + transform: rotate(90deg); +} + +.tooltip { + position: relative; + display: inline-block; + text-decoration: underline; + text-decoration-style: dotted; + text-underline-offset: 3px; +} + +.tooltip.no-underline { + text-decoration: none; +} + +.tooltip .tooltiptext { + visibility: hidden; + + background-color: black; + color: #fff; + + white-space: normal; + text-align: left; + line-height: 1.5; + + max-width: 300px; + width: max-content; + padding: 10px; + + border-radius: 4px; + /* Position the tooltip */ + position: fixed; + transform: translateY(-100%); + z-index: 999; +} + +.tooltip .tooltiptext.layer-tooltiptext { + white-space: normal; + margin-left: 0; + width: 1000px; + max-width: 1000px; + max-height: 200px; + overflow-x: hidden; + overflow-y: auto; +} + +.tooltip:hover .tooltiptext { + visibility: visible; +} + +.severity-count-summary .tooltip { + text-decoration: none; +} + +.flex-box { + display: flex; +} + +/* new added */ +.base-icon .tooltiptext { + margin-left: 0px; + background-color: #292929; +} + +div.title { + visibility: hidden; +} + +#base-image-section { + margin-top: 30px; + border: 1px solid #fff; + border-radius: 8px; + padding: 10px 20px; + max-height: 400px; + overflow-y: visible; + overflow-x: hidden; +} + +#base-image-table { + width: 100%; +} + +#base-image-table th { + text-align: left; + height: 30px; +} + +.layer-entry { + display: flex; + justify-content: space-between; + padding: 0 10px; + border-bottom: 1px dotted #666666; +} + +.base-image-summary { + cursor: pointer; +} + +.base-image-details { + display: block; + margin-bottom: 20px; +} + +.base-image-title { + width: 150px; + color: #9aa0a6; + height: 20px; +} + +.layer-command { + max-width: 1000px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.clickable { + cursor: pointer; +} + +.clickable-layer:hover { + background-color: rgba(255, 255, 255, 0.04); +} + +.source-path { + border: 1px #888 solid; + border-radius: 4px; + padding: 5px; + background-color: rgba(0, 0, 0, 0.2); +} + +.hide-block { + display: none !important; +} diff --git a/internal/output/html/style.html b/internal/output/html/style.html deleted file mode 100644 index 10e43224868..00000000000 --- a/internal/output/html/style.html +++ /dev/null @@ -1,612 +0,0 @@ - diff --git a/internal/output/html/vuln_table_entry_template.gohtml b/internal/output/html/vuln_table_entry_template.gohtml new file mode 100644 index 00000000000..ff47feda3ad --- /dev/null +++ b/internal/output/html/vuln_table_entry_template.gohtml @@ -0,0 +1,47 @@ +{{ $index := uniqueID }} +{{ $element := .Element }} + + + {{ if eq (len $element.GroupIDs) 1 }} +

{{ $element.ID }}
+ {{ else }} +
+
{{ $element.ID }}
+ Group IDs:
+ {{ range $rowIndex, $alias := $element.GroupIDs }} + {{ $alias }}
+ {{ end }} +
+
+ {{ end }} + + + {{ if eq (len $element.Aliases) 1 }} + {{ index $element.Aliases 0 }} + {{ else if gt (len $element.Aliases) 1}} +
+ {{ index $element.Aliases 0}}, ... + + {{ range $rowIndex, $alias := $element.Aliases }} + {{ $alias }}
+ {{ end }} +
+
+ {{ else }} + No Aliases + {{ end }} + + + +

+ {{$element.FixedVersion }}

+ + +
+

{{ $element.SeverityScore }}

+
+ + +

Open in tab

+ + diff --git a/internal/output/html/vuln_table_template.gohtml b/internal/output/html/vuln_table_template.gohtml index cb5b4179876..620e65cc12b 100644 --- a/internal/output/html/vuln_table_template.gohtml +++ b/internal/output/html/vuln_table_template.gohtml @@ -7,80 +7,13 @@ {{ range $rowIndex, $element := .RegularVulns }} - {{ $index := uniqueID }} - - - {{ if eq (len $element.GroupIDs) 1 }} -
{{ $element.ID }}
- {{ else }} -
-
{{ $element.ID }}
- Group IDs: {{ join $element.GroupIDs ", " }} -
- {{ end }} - - - {{ if eq (len $element.Aliases) 1 }} - {{ index $element.Aliases 0 }} - {{ else if gt (len $element.Aliases) 1}} -
- {{ index $element.Aliases 0}}, ... - {{ join $element.Aliases ", " }} -
- {{ end }} - - - -

- {{$element.FixedVersion }}

- - -
-

{{ $element.SeverityScore }}

-
- - -

Open in tab

- - + {{$args := buildVulnTableEntryArgument $element false}} + {{template "vuln_table_entry_template.gohtml" $args}} {{ end }} {{ range $rowIndex, $element := .HiddenVulns }} {{ $index := uniqueID }} - - - {{ if eq (len $element.GroupIDs) 1 }} -
{{ $element.ID }}
- {{ else }} -
-
{{ $element.ID }}
- Group IDs: {{ join $element.GroupIDs ", " }} -
- {{ end }} - - - {{ if eq (len $element.Aliases) 1 }} - {{ index $element.Aliases 0 }} - {{ else if gt (len $element.Aliases) 1}} -
- {{ index $element.Aliases 0}}... - {{ join $element.Aliases ", " }} -
- {{ end }} - - - -

{{$element.FixedVersion }}

- - -
-

{{ $element.SeverityScore }}

-
- - -

Open in tab

- - + {{$args := buildVulnTableEntryArgument $element true}} + {{template "vuln_table_entry_template.gohtml" $args}} {{ end }}