mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-01-15 00:03:59 +01:00
Lazy-load of dependency details
This commit is contained in:
@@ -62,5 +62,24 @@ function updatePosition(){
|
||||
// document.getElementById(…) is used over $('#'+…) in order to reduce attack surface: It does not look like a good idea to pass untrusted input to “omnipotent” `$` function.
|
||||
$.scrollTo(document.getElementById(location.hash.substr(1)), {offset: -$('#navbar').height()});
|
||||
}
|
||||
function lazyLoad(el){
|
||||
var $el = $(el);
|
||||
var url = $el.attr("data-lazyload-url");
|
||||
function setUrl(newUrl){
|
||||
$el.attr("data-lazyload-url", newUrl);
|
||||
}
|
||||
if(url){
|
||||
$el.html("Loading");
|
||||
$el.load(url, function( response, status, xhr ) {
|
||||
if ( status == "error" ) {
|
||||
$el.html("Error when loading data");
|
||||
setUrl(url);
|
||||
}
|
||||
});
|
||||
setUrl(null);
|
||||
}
|
||||
}
|
||||
|
||||
$(window).bind('hashchange', function(e) { updatePosition(); });
|
||||
$(window).bind('load', function(e) { updatePosition(); });
|
||||
$(window).bind('load', function(e) { updatePosition(); });
|
||||
$(window).bind('show.bs.collapse', function(e){ lazyLoad(e.target); });
|
||||
@@ -3,6 +3,7 @@ package binders
|
||||
import java.net.URLDecoder.decode
|
||||
import java.net.URLEncoder.encode
|
||||
|
||||
import com.ysoft.odc.Hashes
|
||||
import play.api.mvc.{JavascriptLiteral, PathBindable, QueryStringBindable}
|
||||
|
||||
object QueryBinders {
|
||||
@@ -21,6 +22,13 @@ object QueryBinders {
|
||||
QueryStringBindable.bindableString.transform(s => formats.reads(Json.parse(s)).getOrElse(Map()), map => formats.writes(map).toString())
|
||||
}
|
||||
|
||||
implicit val hashedBindable = QueryStringBindable.bindableString.transform[Hashes](
|
||||
str => str.split('-') match {
|
||||
case Array(sha1, md5) => Hashes(sha1 = sha1, md5 = md5)
|
||||
},
|
||||
hashes => s"${hashes.sha1}-${hashes.md5}"
|
||||
)
|
||||
|
||||
implicit object MapStringIntJavascriptLiteral extends JavascriptLiteral[Map[String, Int]] {
|
||||
override def to(value: Map[String, Int]): String = formats.writes(value).toString()
|
||||
}
|
||||
|
||||
@@ -121,6 +121,7 @@ object DependencyCheckReportsParser{
|
||||
lazy val groupedDependencies = allDependencies.groupBy(_._1.hashes).values.map(GroupedDependency(_)).toSeq
|
||||
lazy val groupedDependenciesByPlainLibraryIdentifier: Map[PlainLibraryIdentifier, Set[GroupedDependency]] =
|
||||
groupedDependencies.toSet.flatMap((grDep: GroupedDependency) => grDep.plainLibraryIdentifiers.map(_ -> grDep)).groupBy(_._1).mapValues(_.map(_._2)).map(identity)
|
||||
lazy val groupedDependenciesByHashes: Map[Hashes, GroupedDependency] = groupedDependencies.map(gd => gd.hashes -> gd).toMap
|
||||
lazy val vulnerableDependencies = groupedDependencies.filter(_.vulnerabilities.nonEmpty)
|
||||
lazy val suppressedOnlyDependencies = groupedDependencies.filter(gd => gd.vulnerabilities.isEmpty && gd.suppressedIdentifiers.nonEmpty)
|
||||
|
||||
|
||||
@@ -274,6 +274,17 @@ class Statistics @Inject()(
|
||||
}
|
||||
}
|
||||
|
||||
def dependencyDetails(selectorOption: Option[String], depPrefix: String, depId: Hashes) = ReadAction.async { implicit req =>
|
||||
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
|
||||
resultsFuture flatMap { allResults =>
|
||||
println(selectorOption)
|
||||
select(allResults, selectorOption).fold(Future.successful(notFound())) { selection =>
|
||||
val dep = selection.result.groupedDependenciesByHashes(depId)
|
||||
Future.successful(Ok(views.html.dependencyDetailsInner(depPrefix = depPrefix, dep = dep, selectorOption = selectorOption)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def allFiles(selectorOption: Option[String]) = ReadAction.async { implicit req =>
|
||||
val (lastRefreshTime, resultsFuture) = projectReportsProvider.resultsForVersions(versions)
|
||||
resultsFuture flatMap { allResults =>
|
||||
|
||||
16
app/views/dependencyDetails.scala.html
Normal file
16
app/views/dependencyDetails.scala.html
Normal file
@@ -0,0 +1,16 @@
|
||||
@(depPrefix: String, expandByDefault: Boolean, dep: GroupedDependency, selectorOption: Option[String])
|
||||
<div id="@depPrefix-details" class="collapse @if(expandByDefault){ in }" data-lazyload-url="@routes.Statistics.dependencyDetails(
|
||||
depPrefix = depPrefix,
|
||||
depId = dep.hashes,
|
||||
selectorOption = selectorOption
|
||||
)">
|
||||
@if(expandByDefault){
|
||||
@dependencyDetailsInner(
|
||||
depPrefix = depPrefix,
|
||||
selectorOption = selectorOption,
|
||||
dep = dep
|
||||
)
|
||||
}else{
|
||||
placeholder
|
||||
}
|
||||
</div>
|
||||
59
app/views/dependencyDetailsInner.scala.html
Normal file
59
app/views/dependencyDetailsInner.scala.html
Normal file
@@ -0,0 +1,59 @@
|
||||
@(depPrefix: String, dep: GroupedDependency, selectorOption: Option[String])
|
||||
|
||||
@if(dep.descriptions.size > 1){
|
||||
<div class="alert alert-warning">Multiple descriptions for this dependency!</div>
|
||||
}
|
||||
@for(descriptionParagraphs <- dep.parsedDescriptions){
|
||||
<div class="description">
|
||||
@for(descriptionParagraphLines <- descriptionParagraphs){
|
||||
<p>
|
||||
@for(line <- descriptionParagraphLines) {
|
||||
@line<br>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-evidence-details">Evidence</h4>
|
||||
<table id="@depPrefix-evidence-details" class="collapse table table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>confidence</th>
|
||||
<th>evidence type</th>
|
||||
<th>name</th>
|
||||
<th>source</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
@for(fileName <- dep.fileNames.toIndexedSeq.sorted){
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><i>filename</i></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>@fileName</td>
|
||||
</tr>
|
||||
}
|
||||
@for(ev <- dep.dependencies.keySet.map(_.evidenceCollected).flatten){
|
||||
<tr>
|
||||
<td>@ev.confidence
|
||||
<td>@ev.evidenceType
|
||||
<td>@ev.name
|
||||
<td>@ev.source
|
||||
<td>@ev.value
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-projects-details">Affected projects (@dep.projects.size)</h4>
|
||||
<ul id="@depPrefix-projects-details" class="collapse in">@for(p <- dep.projects.toIndexedSeq.sorted){<li>@friendlyProjectName(p)</li>}</ul>
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-vulnerabilities-details">Vulnerabilities (@dep.vulnerabilities.size)</h4>
|
||||
<ul id="@depPrefix-vulnerabilities-details" class="collapse in">
|
||||
@for(vuln <- dep.vulnerabilities.toSeq.sortBy(_.cvssScore.map(-_)); vulnPrefix = s"$depPrefix-vulnerabilities-details-${vuln.name}"){
|
||||
<li>
|
||||
<h5 data-toggle="collapse" data-target="#@vulnPrefix-details">@vuln.name <a href="@routes.Statistics.vulnerability(vuln.name, selectorOption)"><span class="glyphicon glyphicon-log-out"></span></a></h5>
|
||||
<div id="@vulnPrefix-details" class="collapse">
|
||||
@vulnerability("h6", depPrefix, vuln)
|
||||
<h6 data-toggle="collapse" data-target="#@(s"$depPrefix-suppression-cve-${vuln.name}")">CVE suppression</h6>
|
||||
<div id="@(s"$depPrefix-suppression-cve-${vuln.name}")" class="collapse">@SuppressionXml.forVuln(dep, vuln)</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
@@ -24,63 +24,11 @@
|
||||
@for(identifier <- dep.identifiers; cpe <- identifier.toCpeIdentifierOption ) {
|
||||
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cpe-${cpeHtmlId(cpe)}")" class="collapse">@SuppressionXml.forCpe(dep, cpe)</div>
|
||||
}
|
||||
<div id="@depPrefix-details" class="collapse @if(expandByDefault){ in }">
|
||||
@if(dep.descriptions.size > 1){
|
||||
<div class="alert alert-warning">Multiple descriptions for this dependency!</div>
|
||||
}
|
||||
@for(descriptionParagraphs <- dep.parsedDescriptions){
|
||||
<div class="description">
|
||||
@for(descriptionParagraphLines <- descriptionParagraphs){
|
||||
<p>
|
||||
@for(line <- descriptionParagraphLines) {
|
||||
@line<br>
|
||||
}
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-evidence-details">Evidence</h4>
|
||||
<table id="@depPrefix-evidence-details" class="collapse table table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>confidence</th>
|
||||
<th>evidence type</th>
|
||||
<th>name</th>
|
||||
<th>source</th>
|
||||
<th>value</th>
|
||||
</tr>
|
||||
@for(fileName <- dep.fileNames.toIndexedSeq.sorted){
|
||||
<tr>
|
||||
<td></td>
|
||||
<td><i>filename</i></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>@fileName</td>
|
||||
</tr>
|
||||
}
|
||||
@for(ev <- dep.dependencies.keySet.map(_.evidenceCollected).flatten){
|
||||
<tr>
|
||||
<td>@ev.confidence
|
||||
<td>@ev.evidenceType
|
||||
<td>@ev.name
|
||||
<td>@ev.source
|
||||
<td>@ev.value
|
||||
</tr>
|
||||
}
|
||||
</table>
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-projects-details">Affected projects (@dep.projects.size)</h4>
|
||||
<ul id="@depPrefix-projects-details" class="collapse in">@for(p <- dep.projects.toIndexedSeq.sorted){<li>@friendlyProjectName(p)</li>}</ul>
|
||||
<h4 data-toggle="collapse" data-target="#@depPrefix-vulnerabilities-details">Vulnerabilities (@dep.vulnerabilities.size)</h4>
|
||||
<ul id="@depPrefix-vulnerabilities-details" class="collapse in">
|
||||
@for(vuln <- dep.vulnerabilities.toSeq.sortBy(_.cvssScore.map(-_)); vulnPrefix = s"$depPrefix-vulnerabilities-details-${vuln.name}"){
|
||||
<li>
|
||||
<h5 data-toggle="collapse" data-target="#@vulnPrefix-details">@vuln.name <a href="@routes.Statistics.vulnerability(vuln.name, selectorOption)"><span class="glyphicon glyphicon-log-out"></span></a></h5>
|
||||
<div id="@vulnPrefix-details" class="collapse">
|
||||
@vulnerability("h6", s"$idPrefix-${dep.sha1}", vuln)
|
||||
<h6 data-toggle="collapse" data-target="#@(s"$idPrefix-${dep.sha1}-suppression-cve-${vuln.name}")">CVE suppression</h6>
|
||||
<div id="@(s"$idPrefix-${dep.sha1}-suppression-cve-${vuln.name}")" class="collapse">@SuppressionXml.forVuln(dep, vuln)</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@dependencyDetails(
|
||||
depPrefix = depPrefix,
|
||||
expandByDefault = expandByDefault,
|
||||
selectorOption = selectorOption,
|
||||
dep = dep
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ POST /unsnooze/:snoozeId controllers.Application.unsnooze(s
|
||||
GET /https-test/with-redirect controllers.Application.testHttps(allowRedirect: Boolean = true)
|
||||
GET /https-test controllers.Application.testHttps(allowRedirect: Boolean = false)
|
||||
|
||||
GET /stats/dependency-details.htmlf controllers.Statistics.dependencyDetails(selectorOption: Option[String], depPrefix: String, depId: com.ysoft.odc.Hashes)
|
||||
GET /stats/basic controllers.Statistics.basic(selector: Option[String] = None)
|
||||
GET /stats/basic/*selector controllers.Statistics.basic(selector: Option[String])
|
||||
GET /stats/details controllers.Statistics.vulnerabilities(selector: Option[String], tagId: Option[Int])
|
||||
|
||||
Reference in New Issue
Block a user