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; color: white;
font-weight: bold; font-weight: bold;
} }
.dependencies-table .identifiers ul, .dependencies-table .identifiers ul li{
margin-left: 0;
padding-left: 0;
list-style-type: none;
}
.severity{ .severity{
font-size: smaller; font-size: smaller;
margin-bottom: 5px; margin-bottom: 5px;
width: 10px;
} }
.severity .score{ .severity .score{
font-weight: bolder; font-weight: bolder;
} }
.severity .computation-details{
display: block;
}
.explained{ .explained{
border-bottom: 1px dashed black; border-bottom: 1px dashed black;
@@ -101,7 +110,7 @@ h3.library-identification{
height: 1.5em; height: 1.5em;
width: 1.5em; width: 1.5em;
margin-left: 0.5em; margin-left: 0.5em;
font-size: 50%; font-size: 70%;
} }
.explained:hover{ .explained:hover{
border-bottom-color: blue; border-bottom-color: blue;
@@ -156,3 +165,35 @@ h3.library-identification{
.nav > li.collapse.in { .nav > li.collapse.in {
display: list-item; 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){ function lazyLoad(el){
var $el = $(el); var $el = $(el);
var wrapExpr = $el.attr('data-wrapper');
var targetEl = wrapExpr ? $(wrapExpr) : $el;
var url = $el.attr("data-lazyload-url"); var url = $el.attr("data-lazyload-url");
function setUrl(newUrl){ function setUrl(newUrl){
$el.attr("data-lazyload-url", newUrl); $el.attr("data-lazyload-url", newUrl);
} }
if(url){ if(url){
$el.html($('<div class="progress">') if($el != targetEl){
$el.html(targetEl);
}
targetEl.html($('<div class="progress">')
.append( .append(
$('<div class="progress-bar progress-bar-striped active" role="progressbar" style="width: 100%;">Loading…</div>') $('<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" ) { if ( status == "error" ) {
$el.html("Error when loading data"); targetEl.html("Error when loading data");
setUrl(url); setUrl(url);
} }
}); });

View File

@@ -1,5 +1,16 @@
@(depPrefix: String, dep: GroupedDependency, selectorOption: Option[String]) @(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){ @if(dep.descriptions.size > 1){
<div class="alert alert-warning">Multiple descriptions for this dependency!</div> <div class="alert alert-warning">Multiple descriptions for this dependency!</div>
} }

View File

@@ -3,32 +3,74 @@
cpe.getBytes("utf-8").mkString("-") cpe.getBytes("utf-8").mkString("-")
} }
<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}"){ @for(dep <- list; depPrefix = s"$idPrefix-${dep.sha1}"){
<h3 class="library-identification" id="@depPrefix-head" data-toggle="collapse" data-target="#@depPrefix-details"> <tr>
@libraryIdentification(dep, Some(cpe => s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}"), addLink = false, addButtons = addButtons) <td class="severity">
@for(s <- dep.maxCvssScore) { @for(s <- dep.maxCvssScore) {
<span class="severity"> <span class="score">@dep.ysdssScore.map("%.2f".format(_))</span>
(<span title="highest vulnerability score" class="explained">@s</span> <span class="computation-details">
× <span class="explained" title="affected project count">@dep.projects.size</span> = <span class="score-vulnerability">@s</span> × <span class="score-projects">@dep.projects.size</span>
= <span class="explained score" title="total score">@dep.ysdssScore</span>)
(vulns:&nbsp;@dep.vulnerabilities.size)
</span> </span>
} }
@dep.cpeIdentifiers.toSeq match { </td>
case Seq() => {} <td class="identifiers">
case cpeIds => { @libraryIdentificationList(dep, Some(cpe => s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}"), addLink = false, addButtons = addButtons)
<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> </td>
} <td class="vulns">@for(s <- dep.maxCvssScore) {@dep.vulnerabilities.size}</td>
} <td class="actions">
</h3> <button data-toggle="collapse" data-target="#@depPrefix-details" class="btn btn-info"><span class="glyphicon glyphicon-chevron-down"></span></button>
@for(identifier <- dep.identifiers; cpe <- identifier.toCpeIdentifierOption ) { </td>
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}")" class="collapse">@SuppressionXml.forCpe(dep, cpe)</div> </tr>
} <tr data-wrapper="<td colspan='4' xxxstyle='width: 100%;'></td>" id="@depPrefix-details" class="details collapse" data-lazyload-url="@routes.Statistics.dependencyDetails(
@dependencyDetails(
depPrefix = depPrefix, depPrefix = depPrefix,
expandByDefault = expandByDefault, depId = dep.hashes,
selectorOption = selectorOption, selectorOption = selectorOption
dep = dep )"></tr>
)
} }
</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: Identifier, addLink: Boolean = true)
@identifier.confidence.toString.toLowerCase: @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>}
@secureLink(if(addLink) identifier.url else ""){@identifier.name}

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>