Major vulnerable libraries UI redesign

This commit is contained in:
Šesták Vít
2017-03-20 14:34:34 +01:00
parent 5d1280e6c7
commit 41b4873847
6 changed files with 162 additions and 35 deletions

View File

@@ -75,15 +75,24 @@ h3.library-identification{
color: white;
font-weight: bold;
}
.dependencies-table .identifiers ul, .dependencies-table .identifiers ul li{
margin-left: 0;
padding-left: 0;
list-style-type: none;
}
.severity{
font-size: smaller;
margin-bottom: 5px;
width: 10px;
}
.severity .score{
font-weight: bolder;
}
.severity .computation-details{
display: block;
}
.explained{
border-bottom: 1px dashed black;
@@ -101,7 +110,7 @@ h3.library-identification{
height: 1.5em;
width: 1.5em;
margin-left: 0.5em;
font-size: 50%;
font-size: 70%;
}
.explained:hover{
border-bottom-color: blue;
@@ -155,4 +164,36 @@ h3.library-identification{
}
.nav > li.collapse.in {
display: list-item;
}
.dependencies-table .vulns{
text-align: right;
}
.dependencies-table .actions{
vertical-align: bottom;
width: 10px;
}
.dependencies-table > tbody > tr:nth-of-type(4n+1), .dependencies-table > tbody > tr:nth-of-type(4n+2) {
background-color: #f9f9f9;
}
.dependencies-table > tbody > tr.details > td {
border-top-style: none;
}
.dependencies-table .severity .explained:hover {
color:blue;
}
.dependencies-table .severity .explained:after {
content: "";
display: none;
}
.dependencies-table .severity .tooltip-inner {
white-space:nowrap;
max-width:none;
}
.dependencies-table .identifiers .tooltip-inner {
max-width: 350px;
width: 350px;
}
.dependencies-table .identifiers .explained{
border-bottom: none;
}

View File

@@ -64,19 +64,24 @@ function updatePosition(){
}
function lazyLoad(el){
var $el = $(el);
var wrapExpr = $el.attr('data-wrapper');
var targetEl = wrapExpr ? $(wrapExpr) : $el;
var url = $el.attr("data-lazyload-url");
function setUrl(newUrl){
$el.attr("data-lazyload-url", newUrl);
$el.attr("data-lazyload-url", newUrl);
}
if(url){
$el.html($('<div class="progress">')
if($el != targetEl){
$el.html(targetEl);
}
targetEl.html($('<div class="progress">')
.append(
$('<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 100%;">Loading…</div>')
)
);
$el.load(url, function( response, status, xhr ) {
targetEl.load(url, function( response, status, xhr ) {
if ( status == "error" ) {
$el.html("Error when loading data");
targetEl.html("Error when loading data");
setUrl(url);
}
});

View File

@@ -1,5 +1,16 @@
@(depPrefix: String, dep: GroupedDependency, selectorOption: Option[String])
@dep.cpeIdentifiers.toSeq match {
case Seq() => {}
case cpeIds => {
<p>
<a href="@routes.Statistics.searchVulnerableSoftware(
cpeIds.map(_.name.split(':').take(4).mkString(":")).toSeq, None
)" title="Search for known vulnerabilities" class="btn btn-default">Look for vulnerabilities in other versions</a>
</p>
}
}
@if(dep.descriptions.size > 1){
<div class="alert alert-warning">Multiple descriptions for this dependency!</div>
}

View File

@@ -3,32 +3,74 @@
cpe.getBytes("utf-8").mkString("-")
}
@for(dep <- list; depPrefix = s"$idPrefix-${dep.sha1}"){
<h3 class="library-identification" id="@depPrefix-head" data-toggle="collapse" data-target="#@depPrefix-details">
@libraryIdentification(dep, Some(cpe => s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}"), addLink = false, addButtons = addButtons)
@for(s <- dep.maxCvssScore) {
<span class="severity">
(<span title="highest vulnerability score" class="explained">@s</span>
× <span class="explained" title="affected project count">@dep.projects.size</span>
= <span class="explained score" title="total score">@dep.ysdssScore</span>)
(vulns:&nbsp;@dep.vulnerabilities.size)
</span>
}
@dep.cpeIdentifiers.toSeq match {
case Seq() => {}
case cpeIds => {
<a href="@routes.Statistics.searchVulnerableSoftware(cpeIds.map(_.name.split(':').take(4).mkString(":")).toSeq, None)" title="Search for known vulnerabilities"><span class="glyphicon glyphicon-flash"></span></a>
}
}
</h3>
@for(identifier <- dep.identifiers; cpe <- identifier.toCpeIdentifierOption ) {
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}")" class="collapse">@SuppressionXml.forCpe(dep, cpe)</div>
<table class="table table-condensed dependencies-table">
<thead>
<tr>
<th>Severity</th>
<th>Identifiers</th>
<th class="vulns">Vulns</th>
<th class="actions"></th>
</tr>
</thead>
@for(dep <- list; depPrefix = s"$idPrefix-${dep.sha1}"){
<tr>
<td class="severity">
@for(s <- dep.maxCvssScore) {
<span class="score">@dep.ysdssScore.map("%.2f".format(_))</span>
<span class="computation-details">
= <span class="score-vulnerability">@s</span> × <span class="score-projects">@dep.projects.size</span>
</span>
}
</td>
<td class="identifiers">
@libraryIdentificationList(dep, Some(cpe => s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}"), addLink = false, addButtons = addButtons)
</td>
<td class="vulns">@for(s <- dep.maxCvssScore) {@dep.vulnerabilities.size}</td>
<td class="actions">
<button data-toggle="collapse" data-target="#@depPrefix-details" class="btn btn-info"><span class="glyphicon glyphicon-chevron-down"></span></button>
</td>
</tr>
<tr data-wrapper="<td colspan='4' xxxstyle='width: 100%;'></td>" id="@depPrefix-details" class="details collapse" data-lazyload-url="@routes.Statistics.dependencyDetails(
depPrefix = depPrefix,
depId = dep.hashes,
selectorOption = selectorOption
)"></tr>
}
@dependencyDetails(
depPrefix = depPrefix,
expandByDefault = expandByDefault,
selectorOption = selectorOption,
dep = dep
)
}
</table>
<script type="text/javascript">
$(function () {
$(".severity .score")
.attr("title", "total score = score of highest-rated vulnerability × number of affected projects")
.addClass("explained")
.tooltip({ placement: "top" });
$(".severity .score-vulnerability")
.attr("title", "score of highest-rated vulnerability")
.addClass("explained")
.tooltip({ placement: "bottom" });
$(".severity .score-projects")
.attr("title", "number of affected projects")
.addClass("explained")
.tooltip({ placement: "bottom" });
var identifierTypes = {
"cpe": "Common Platform Enumeration (CPE)",
"maven": "Maven",
"file": "File name"
};
var identifierTypesDetails = {
"cpe": "This identifier is used in National Vulnerability Database, so it is important for proper matching of vulnerabilities. A mismatched CPE identifier can directly cause wrongly matched vulnerabilities.",
"file": "This identifier is shown only if there is no identifier with high or highest confidence."
};
function escapeHtml(s) {
return document.createElement("div").appendChild(document.createTextNode(s)).parentNode.innerHTML;
}
$(".identifiers .identifier").each(function(i, obj){
var $obj = $(obj);
var identifierType = $obj.attr("data-type");
var confidence = $obj.attr("data-confidence");
var identifierTypeFriendlyName = identifierTypes[identifierType] || identifierType;
var identifiedDetails = identifierTypesDetails[identifierType];
var title = "<b>"+escapeHtml(identifierTypeFriendlyName)+ "</b> identifier matched with <b>"+escapeHtml(confidence)+"</b> confidence. "+(identifiedDetails ? ("<hr>"+identifiedDetails) :"");
$obj.addClass("explained").attr("title", title).tooltip({placement: "right", html: true});
});
});
</script>

View File

@@ -1,3 +1,2 @@
@(identifier: Identifier, addLink: Boolean = true)
@identifier.confidence.toString.toLowerCase:
@secureLink(if(addLink) identifier.url else ""){@identifier.name}
@secureLink(if(addLink) identifier.url else ""){<span class="identifier identifier-@identifier.confidence.toString.toLowerCase" data-confidence="@identifier.confidence.toString.toLowerCase" data-type="@identifier.identifierType">@identifier.name</span>}

View File

@@ -0,0 +1,29 @@
@(dep: GroupedDependency, suppressionXmlIdOption: Option[String => String] = None, addLink: Boolean = true, addButtons: Boolean = true)
@import com.ysoft.odc.Confidence
@implicitOrdering = @{
// This implicit should not be theoretically needed, but missing this used to cause somehow non-deterministic scalac behavior:
// The first ….sortBy expression used to pass, while the second one used to fail sometimes. Even though both expressions are essentially the same.
// When these expressions are swapped, still the first (after swapping) one passes and the second one fails, no matter which one is the first and
// which is the second. So, it looks like some compiler bug related to mutable state.
// It also seems to be related to some compiler cache. It is somehow possible to compile it by incremental compilation and some code changes, but
// clean build deterministically fails at the second expression.
// So, making the implicit explicit is a workaround for this issue.
Ordering.Tuple4[Confidence.Value, String, String, String]
}
<ul>
@if(!dep.identifiers.exists(_.confidence >= Confidence.High)){
<li><span class="identifier identifier-file" data-type="file" data-confidence="highest">@dep.fileNames.toSeq.sorted.mkString(", ")@if(addButtons){<span class="btn-xs library-identification-badge-hack">&nbsp;</span>}</span></li>
}
@for(id <- dep.identifiers.toSeq.sortBy(i => (i.confidence, i.identifierType, i.name, i.url)).reverse){
<li>
@identifier(id, addLink)
@for(cpe <- id.toCpeIdentifierOption; suppressionXmlId <- suppressionXmlIdOption; if addButtons){
<button class="btn btn-default btn-xs" data-toggle="collapse" data-target="#@suppressionXmlId(cpe)">×</button>
}
@if(addButtons && suppressionXmlIdOption.isDefined){<span class="btn-xs library-identification-badge-hack">&nbsp;</span>}
</li>
}
@for(id <- dep.suppressedIdentifiers.toSeq.sortBy(i => (i.confidence, i.identifierType, i.name, i.url)).reverse){
<li><del>@identifier(id, addLink)</del></li>
}
</ul>