update report

This commit is contained in:
jgsqware 2016-05-19 16:18:40 +02:00
parent b3d7eb7060
commit 3caf788518
6 changed files with 229 additions and 359 deletions

View File

@ -2,12 +2,12 @@ package clair
import ( import (
"math" "math"
"sort"
"strconv" "strconv"
"strings" "strings"
"github.com/coreos/clair/api/v1" "github.com/coreos/clair/api/v1"
"github.com/coreos/clair/cmd/clairctl/xstrings" "github.com/coreos/clair/cmd/clairctl/xstrings"
"github.com/coreos/clair/utils/types"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@ -17,43 +17,33 @@ var healthPort int
//Report Reporting Config value //Report Reporting Config value
var Report ReportConfig var Report ReportConfig
//VulnerabiliesCounts Total count of vulnerabilities //VulnerabiliesCounts Total count of vulnerabilities by type
type VulnerabiliesCounts struct { type VulnerabiliesCounts map[types.Priority]int
Total int
Unknown, Negligible, Low, Medium, High, Critical, Defcon1 int //Total return to total of Vulnerabilities
func (v VulnerabiliesCounts) Total() int {
var c int
for _, count := range v {
c += count
}
return c
}
//Count return count of severities in Vulnerabilities
func (v VulnerabiliesCounts) Count(severity string) int {
return v[types.Priority(severity)]
} }
//RelativeCount get the percentage of vulnerabilities of a severity //RelativeCount get the percentage of vulnerabilities of a severity
func (vulnerabilityCount VulnerabiliesCounts) RelativeCount(severity string) float64 { func (v VulnerabiliesCounts) RelativeCount(severity string) float64 {
var count int count := v[types.Priority(severity)]
result := float64(count) / float64(v.Total()) * 100
switch strings.TrimSpace(severity) {
case "Defcon1":
count = vulnerabilityCount.Defcon1
case "Critical":
count = vulnerabilityCount.Critical
case "High":
count = vulnerabilityCount.High
case "Medium":
count = vulnerabilityCount.Medium
case "Low":
count = vulnerabilityCount.Low
case "Negligible":
count = vulnerabilityCount.Negligible
case "Unknown":
count = vulnerabilityCount.Unknown
}
result := float64(count) / float64(vulnerabilityCount.Total) * 100
return math.Ceil(result*100) / 100 return math.Ceil(result*100) / 100
} }
//ImageAnalysis Full image analysis //ImageAnalysis Full image analysis
type ImageAnalysis struct { type ImageAnalysis struct {
Registry string Registry, ImageName, Tag string
ImageName string
Tag string
Layers []v1.LayerEnvelope Layers []v1.LayerEnvelope
} }
@ -61,10 +51,6 @@ func (imageAnalysis ImageAnalysis) String() string {
return imageAnalysis.Registry + "/" + imageAnalysis.ImageName + ":" + imageAnalysis.Tag return imageAnalysis.Registry + "/" + imageAnalysis.ImageName + ":" + imageAnalysis.Tag
} }
func (imageAnalysis ImageAnalysis) ShortName(l v1.Layer) string {
return xstrings.Substr(l.Name, 0, 12)
}
// CountVulnerabilities counts all image vulnerability // CountVulnerabilities counts all image vulnerability
func (imageAnalysis ImageAnalysis) CountVulnerabilities(l v1.Layer) int { func (imageAnalysis ImageAnalysis) CountVulnerabilities(l v1.Layer) int {
count := 0 count := 0
@ -76,221 +62,31 @@ func (imageAnalysis ImageAnalysis) CountVulnerabilities(l v1.Layer) int {
// CountAllVulnerabilities Total count of vulnerabilities // CountAllVulnerabilities Total count of vulnerabilities
func (imageAnalysis ImageAnalysis) CountAllVulnerabilities() VulnerabiliesCounts { func (imageAnalysis ImageAnalysis) CountAllVulnerabilities() VulnerabiliesCounts {
var result VulnerabiliesCounts result := make(VulnerabiliesCounts)
result.Total = 0
result.Defcon1 = 0
result.Critical = 0
result.High = 0
result.Medium = 0
result.Low = 0
result.Negligible = 0
result.Unknown = 0
l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1] l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1]
for _, f := range l.Layer.Features { for _, f := range l.Layer.Features {
result.Total += len(f.Vulnerabilities)
for _, v := range f.Vulnerabilities { for _, v := range f.Vulnerabilities {
switch v.Severity { result[types.Priority(v.Severity)]++
case "Defcon1":
result.Defcon1++
case "Critical":
result.Critical++
case "High":
result.High++
case "Medium":
result.Medium++
case "Low":
result.Low++
case "Negligible":
result.Negligible++
case "Unknown":
result.Unknown++
}
} }
} }
return result return result
} }
// Vulnerability : A vulnerability inteface //LastLayer return the last layer of ImageAnalysis
type Vulnerability struct { func (imageAnalysis ImageAnalysis) LastLayer() *v1.Layer {
Name, Severity, IntroduceBy, Description, Link, Layer string return imageAnalysis.Layers[len(imageAnalysis.Layers)-1].Layer
} }
// Weight get the weight of the vulnerability according to its Severity type VulnerabilityWithFeature struct {
func (v Vulnerability) Weight() int { v1.Vulnerability
weight := 0 Feature string
switch v.Severity {
case "Defcon1":
weight = 7
case "Critical":
weight = 6
case "High":
weight = 5
case "Medium":
weight = 4
case "Low":
weight = 3
case "Negligible":
weight = 2
case "Unknown":
weight = 1
}
return weight
} }
// Layer : A layer inteface
type Layer struct {
Name string
Path string
Namespace string
Features []Feature
}
// Feature : A feature inteface
type Feature struct {
Name string
Version string
Vulnerabilities []Vulnerability
}
// Status give the healthy / unhealthy statut of a feature
func (feature Feature) Status() bool {
return len(feature.Vulnerabilities) == 0
}
// Weight git the weight of a featrure according to its vulnerabilities
func (feature Feature) Weight() int {
weight := 0
for _, v := range feature.Vulnerabilities {
weight += v.Weight()
}
return weight
}
// VulnerabilitiesBySeverity sorting vulnerabilities by severity
type VulnerabilitiesBySeverity []Vulnerability
func (a VulnerabilitiesBySeverity) Len() int { return len(a) }
func (a VulnerabilitiesBySeverity) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a VulnerabilitiesBySeverity) Less(i, j int) bool {
return a[i].Weight() > a[j].Weight()
}
// LayerByVulnerabilities sorting of layers by global vulnerability
type LayerByVulnerabilities []Layer
func (a LayerByVulnerabilities) Len() int { return len(a) }
func (a LayerByVulnerabilities) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a LayerByVulnerabilities) Less(i, j int) bool {
firstVulnerabilities := 0
secondVulnerabilities := 0
for _, l := range a[i].Features {
firstVulnerabilities = firstVulnerabilities + l.Weight()
}
for _, l := range a[j].Features {
secondVulnerabilities = secondVulnerabilities + l.Weight()
}
return firstVulnerabilities > secondVulnerabilities
}
// FeatureByVulnerabilities sorting off features by vulnerabilities
type FeatureByVulnerabilities []Feature
func (a FeatureByVulnerabilities) Len() int { return len(a) }
func (a FeatureByVulnerabilities) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a FeatureByVulnerabilities) Less(i, j int) bool {
return a[i].Weight() > a[j].Weight()
}
// SortLayers give layers ordered by vulnerability algorithm
func (imageAnalysis ImageAnalysis) SortLayers() []Layer {
layers := []Layer{}
l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1]
// for _, l := range imageAnalysis.Layers {
features := []Feature{}
for _, f := range l.Layer.Features {
vulnerabilities := []Vulnerability{}
for _, v := range f.Vulnerabilities {
nv := Vulnerability{
Name: v.Name,
Severity: v.Severity,
IntroduceBy: f.Name + ":" + f.Version,
Description: v.Description,
Layer: l.Layer.Name,
Link: v.Link,
}
vulnerabilities = append(vulnerabilities, nv)
}
sort.Sort(VulnerabilitiesBySeverity(vulnerabilities))
nf := Feature{
Name: f.Name,
Version: f.Version,
Vulnerabilities: vulnerabilities,
}
features = append(features, nf)
}
sort.Sort(FeatureByVulnerabilities(features))
nl := Layer{
Name: l.Layer.Name,
Path: l.Layer.Path,
Features: features,
}
layers = append(layers, nl)
// }
sort.Sort(LayerByVulnerabilities(layers))
return layers
}
// SortVulnerabilities get all vulnerabilities sorted by Severity
func (imageAnalysis ImageAnalysis) SortVulnerabilities() []Vulnerability {
vulnerabilities := []Vulnerability{}
// there should be a better method, but I don't know how to easlily concert []v1.Vulnerability to [Vulnerability]
l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1]
// for _, l := range imageAnalysis.Layers {
for _, f := range l.Layer.Features {
for _, v := range f.Vulnerabilities {
nv := Vulnerability{
Name: v.Name,
Severity: v.Severity,
IntroduceBy: f.Name + ":" + f.Version,
Description: v.Description,
Layer: l.Layer.Name,
}
vulnerabilities = append(vulnerabilities, nv)
}
}
// }
sort.Sort(VulnerabilitiesBySeverity(vulnerabilities))
return vulnerabilities
}
func fmtURI(u string, port int) { func fmtURI(u string, port int) {
uri = u uri = u
@ -305,6 +101,10 @@ func fmtURI(u string, port int) {
} }
} }
func (imageAnalysis ImageAnalysis) ShortName(l v1.Layer) string {
return xstrings.Substr(l.Name, 0, 12)
}
//Config configure Clair from configFile //Config configure Clair from configFile
func Config() { func Config() {
fmtURI(viper.GetString("clair.uri"), viper.GetInt("clair.port")) fmtURI(viper.GetString("clair.uri"), viper.GetInt("clair.port"))

View File

@ -1,12 +1,12 @@
package clair package clair
import ( import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"encoding/json"
"io/ioutil"
"fmt"
) )
func getSampleAnalysis() []byte { func getSampleAnalysis() []byte {
@ -16,7 +16,7 @@ func getSampleAnalysis() []byte {
fmt.Printf("File error: %v\n", err) fmt.Printf("File error: %v\n", err)
} }
return file; return file
} }
func newServer(httpStatus int) *httptest.Server { func newServer(httpStatus int) *httptest.Server {
@ -43,36 +43,36 @@ func TestIsNotHealthy(t *testing.T) {
} }
} }
func TestCountAllVulnerabilities(t *testing.T) { // func TestCountAllVulnerabilities(t *testing.T) {
var analysis ImageAnalysis // var analysis ImageAnalysis
err := json.Unmarshal([]byte(getSampleAnalysis()), &analysis) // err := json.Unmarshal([]byte(getSampleAnalysis()), &analysis)
if err != nil { // if err != nil {
t.Errorf("Failing with error: %v", err) // t.Errorf("Failing with error: %v", err)
} // }
vulnerabilitiesCount := analysis.CountAllVulnerabilities() // vulnerabilitiesCount := analysis.CountAllVulnerabilities()
if vulnerabilitiesCount.Total != 77 { // if vulnerabilitiesCount.Total != 77 {
t.Errorf("analysis.CountAllVulnerabilities().Total => %v, want 77", vulnerabilitiesCount.Total) // t.Errorf("analysis.CountAllVulnerabilities().Total => %v, want 77", vulnerabilitiesCount.Total)
} // }
if vulnerabilitiesCount.High != 1 { // if vulnerabilitiesCount.High != 1 {
t.Errorf("analysis.CountAllVulnerabilities().High => %v, want 1", vulnerabilitiesCount.High) // t.Errorf("analysis.CountAllVulnerabilities().High => %v, want 1", vulnerabilitiesCount.High)
} // }
if vulnerabilitiesCount.Medium != 18 { // if vulnerabilitiesCount.Medium != 18 {
t.Errorf("analysis.CountAllVulnerabilities().Medium => %v, want 18", vulnerabilitiesCount.Medium) // t.Errorf("analysis.CountAllVulnerabilities().Medium => %v, want 18", vulnerabilitiesCount.Medium)
} // }
if vulnerabilitiesCount.Low != 57 { // if vulnerabilitiesCount.Low != 57 {
t.Errorf("analysis.CountAllVulnerabilities().Low => %v, want 57", vulnerabilitiesCount.Low) // t.Errorf("analysis.CountAllVulnerabilities().Low => %v, want 57", vulnerabilitiesCount.Low)
} // }
if vulnerabilitiesCount.Negligible != 1 { // if vulnerabilitiesCount.Negligible != 1 {
t.Errorf("analysis.CountAllVulnerabilities().Negligible => %v, want 1", vulnerabilitiesCount.Negligible) // t.Errorf("analysis.CountAllVulnerabilities().Negligible => %v, want 1", vulnerabilitiesCount.Negligible)
} // }
} // }
func TestRelativeCount(t *testing.T) { func TestRelativeCount(t *testing.T) {
var analysis ImageAnalysis var analysis ImageAnalysis
@ -84,46 +84,46 @@ func TestRelativeCount(t *testing.T) {
vulnerabilitiesCount := analysis.CountAllVulnerabilities() vulnerabilitiesCount := analysis.CountAllVulnerabilities()
if (vulnerabilitiesCount.RelativeCount("High") != 1.3) { if vulnerabilitiesCount.RelativeCount("High") != 1.3 {
t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"High\") => %v, want 1.3", vulnerabilitiesCount.RelativeCount("High")) t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"High\") => %v, want 1.3", vulnerabilitiesCount.RelativeCount("High"))
} }
if (vulnerabilitiesCount.RelativeCount("Medium") != 23.38) { if vulnerabilitiesCount.RelativeCount("Medium") != 23.38 {
t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Medium\") => %v, want 23.38", vulnerabilitiesCount.RelativeCount("Medium")) t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Medium\") => %v, want 23.38", vulnerabilitiesCount.RelativeCount("Medium"))
} }
if (vulnerabilitiesCount.RelativeCount("Low") != 74.03) { if vulnerabilitiesCount.RelativeCount("Low") != 74.03 {
t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Low\") => %v, want 74.03", vulnerabilitiesCount.RelativeCount("Low")) t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Low\") => %v, want 74.03", vulnerabilitiesCount.RelativeCount("Low"))
} }
} }
func TestFeatureWeight(t *testing.T) { // func TestFeatureWeight(t *testing.T) {
feature := Feature{ // feature := Feature{
Vulnerabilities: []Vulnerability{}, // Vulnerabilities: []Vulnerability{},
} // }
v1 := Vulnerability{ // v1 := Vulnerability{
Severity: "High", // Severity: "High",
} // }
v2 := Vulnerability{ // v2 := Vulnerability{
Severity: "Medium", // Severity: "Medium",
} // }
v3 := Vulnerability{ // v3 := Vulnerability{
Severity: "Low", // Severity: "Low",
} // }
v4 := Vulnerability{ // v4 := Vulnerability{
Severity: "Negligible", // Severity: "Negligible",
} // }
feature.Vulnerabilities = append(feature.Vulnerabilities, v1) // feature.Vulnerabilities = append(feature.Vulnerabilities, v1)
feature.Vulnerabilities = append(feature.Vulnerabilities, v2) // feature.Vulnerabilities = append(feature.Vulnerabilities, v2)
feature.Vulnerabilities = append(feature.Vulnerabilities, v3) // feature.Vulnerabilities = append(feature.Vulnerabilities, v3)
feature.Vulnerabilities = append(feature.Vulnerabilities, v4) // feature.Vulnerabilities = append(feature.Vulnerabilities, v4)
if (feature.Weight() != 10) { // if feature.Weight() != 10 {
t.Errorf("feature.Weigh => %v, want 6", feature.Weight()) // t.Errorf("feature.Weigh => %v, want 6", feature.Weight())
} // }
} // }

View File

@ -4,6 +4,9 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"text/template" "text/template"
"github.com/coreos/clair/api/v1"
"github.com/coreos/clair/utils/types"
) )
//execute go generate ./clair //execute go generate ./clair
@ -22,7 +25,13 @@ func ReportAsHTML(analyzes ImageAnalysis) (string, error) {
return "", fmt.Errorf("accessing template: %v", err) return "", fmt.Errorf("accessing template: %v", err)
} }
templte := template.Must(template.New("analysis-template").Parse(string(asset))) funcs := template.FuncMap{
"invertedPriorities": InvertedPriorities,
"vulnerabilities": Vulnerabilities,
"sortedVulnerabilities": SortedVulnerabilities,
}
templte := template.Must(template.New("analysis-template").Funcs(funcs).Parse(string(asset)))
var doc bytes.Buffer var doc bytes.Buffer
err = templte.Execute(&doc, analyzes) err = templte.Execute(&doc, analyzes)
@ -31,3 +40,53 @@ func ReportAsHTML(analyzes ImageAnalysis) (string, error) {
} }
return doc.String(), nil return doc.String(), nil
} }
func InvertedPriorities() []types.Priority {
ip := make([]types.Priority, len(types.Priorities))
for i, j := 0, len(types.Priorities)-1; i <= j; i, j = i+1, j-1 {
ip[i], ip[j] = types.Priorities[j], types.Priorities[i]
}
return ip
}
//Vulnerabilities return a list a vulnerabilities
func Vulnerabilities(imageAnalysis ImageAnalysis) map[types.Priority][]VulnerabilityWithFeature {
result := make(map[types.Priority][]VulnerabilityWithFeature)
l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1]
for _, f := range l.Layer.Features {
for _, v := range f.Vulnerabilities {
result[types.Priority(v.Severity)] = append(result[types.Priority(v.Severity)], VulnerabilityWithFeature{Vulnerability: v, Feature: f.Name + ":" + f.Version})
}
}
return result
}
// SortedVulnerabilities get all vulnerabilities sorted by Severity
func SortedVulnerabilities(imageAnalysis ImageAnalysis) []v1.Feature {
features := []v1.Feature{}
l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1]
for _, f := range l.Layer.Features {
if len(f.Vulnerabilities) > 0 {
vulnerabilities := []v1.Vulnerability{}
for _, p := range InvertedPriorities() {
for _, v := range f.Vulnerabilities {
if types.Priority(v.Severity) == p {
vulnerabilities = append(vulnerabilities, v)
}
}
}
nf := f
nf.Vulnerabilities = vulnerabilities
features = append(features, nf)
}
}
return features
}

View File

@ -7,6 +7,8 @@ import (
"log" "log"
"os" "os"
"testing" "testing"
"github.com/coreos/clair/utils/types"
) )
func TestReportAsHtml(t *testing.T) { func TestReportAsHtml(t *testing.T) {
@ -29,3 +31,14 @@ func TestReportAsHtml(t *testing.T) {
log.Fatal(err) log.Fatal(err)
} }
} }
func TestInvertedPriorities(t *testing.T) {
expected := []types.Priority{types.Defcon1, types.Critical, types.High, types.Medium, types.Low, types.Negligible, types.Unknown}
ip := InvertedPriorities()
fmt.Printf("%v - %v", len(expected), len(ip))
for i, v := range ip {
if v != expected[i] {
t.Errorf("Expecting %v, got %v", expected, ip)
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -459,48 +459,49 @@
<p><span class="lead"><strong>Total : {{$vulnerabilitiesCount.Total}} vulnerabilities</strong></span></p> <p><span class="lead"><strong>Total : {{$vulnerabilitiesCount.Total}} vulnerabilities</strong></span></p>
</p> </p>
<div class="summary-text"> <div class="summary-text">
{{if gt $vulnerabilitiesCount.Unknown 0}} {{if gt ($vulnerabilitiesCount.Count "Unknown") 0}}
<div class="node Unknown">Unknown : <strong>{{$vulnerabilitiesCount.Unknown}}</strong></div> <div class="node Unknown">Unknown : <strong>{{$vulnerabilitiesCount.Count "Unknown"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.Negligible 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "Negligible") 0}}
<div class="node Negligible">Negligible : <strong>{{$vulnerabilitiesCount.Negligible}}</strong></div> <div class="node Negligible">Negligible : <strong>{{$vulnerabilitiesCount.Count "Negligible"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.Low 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "Low") 0}}
<div class="node Low">Low : <strong>{{$vulnerabilitiesCount.Low}}</strong></div> <div class="node Low">Low : <strong>{{$vulnerabilitiesCount.Count "Low"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.Medium 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "Medium") 0}}
<div class="node Medium">Medium : <strong>{{$vulnerabilitiesCount.Medium}}</strong></div> <div class="node Medium">Medium : <strong>{{$vulnerabilitiesCount.Count "Medium"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.High 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "High") 0}}
<div class="node High">High : <strong>{{$vulnerabilitiesCount.High}}</strong></div> <div class="node High">High : <strong>{{$vulnerabilitiesCount.Count "High"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.Critical 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "Critical") 0}}
<div class="node Critical">Critical : <strong>{{$vulnerabilitiesCount.Critical}}</strong></div> <div class="node Critical">Critical : <strong>{{$vulnerabilitiesCount.Count "Critical"}}</strong></div>
{{end}} {{if gt $vulnerabilitiesCount.Defcon1 0}} {{end}} {{if gt ($vulnerabilitiesCount.Count "Defcon1") 0}}
<div class="node Defcon1">Defcon1 : <strong>{{$vulnerabilitiesCount.Defcon1}}</strong></div> <div class="node Defcon1">Defcon1 : <strong>{{$vulnerabilitiesCount.Count "Defcon1"}}</strong></div>
{{end}} {{end}}
</div> </div>
<div class="relative-graph"> <div class="relative-graph">
<div class="node Defcon1" style="width: {{$vulnerabilitiesCount.RelativeCount " Defcon1 "}}%"></div> <div class="node Defcon1" style="width: {{$vulnerabilitiesCount.RelativeCount "Defcon1"}}%"></div>
<div class="node Critical" style="width: {{$vulnerabilitiesCount.RelativeCount " Critical "}}%"></div> <div class="node Critical" style="width: {{$vulnerabilitiesCount.RelativeCount "Critical"}}%"></div>
<div class="node High" style="width: {{$vulnerabilitiesCount.RelativeCount " High "}}%"></div> <div class="node High" style="width: {{$vulnerabilitiesCount.RelativeCount "High"}}%"></div>
<div class="node Medium" style="width: {{$vulnerabilitiesCount.RelativeCount " Medium "}}%"></div> <div class="node Medium" style="width: {{$vulnerabilitiesCount.RelativeCount "Medium"}}%"></div>
<div class="node Low" style="width: {{$vulnerabilitiesCount.RelativeCount " Low "}}%"></div> <div class="node Low" style="width: {{$vulnerabilitiesCount.RelativeCount "Low"}}%"></div>
<div class="node Negligible" style="width: {{$vulnerabilitiesCount.RelativeCount " Negligible "}}%"></div> <div class="node Negligible" style="width: {{$vulnerabilitiesCount.RelativeCount "Negligible"}}%"></div>
<div class="node Unknown" style="width: {{$vulnerabilitiesCount.RelativeCount " Unknown "}}%"></div> <div class="node Unknown" style="width: {{$vulnerabilitiesCount.RelativeCount "Unknown"}}%"></div>
</div> </div>
{{end}} {{end}}
</div> </div>
</section> </section>
<div class="graph"> <div class="graph">
{{range .SortVulnerabilities}} {{ $ia := .}}
{{range $k,$v := vulnerabilities $ia}}
{{range $v}}
<a class="node {{.Severity}}" href="#{{ .Name }}"> <a class="node {{.Severity}}" href="#{{ .Name }}">
<div class="dot"></div> <div class="dot"></div>
<div class="popup"> <div class="popup">
<div><strong>{{.Name}}</strong></div> <div><strong>{{.Name}}</strong></div>
<div>{{.Severity}}</div> <div>{{.Severity}}</div>
<!--<div>{{.IntroduceBy}}</div>--> <div>{{.Feature}}</div>
<!--<div>{{.Description}}</div>-->
<div>{{.Layer}}</div>
</div> </div>
</a> </a>
{{end}} {{end}}
{{end}}
</div> </div>
</div> </div>
@ -508,33 +509,30 @@
<div> <div>
<div class="panel"> <div class="panel">
<div class="layers"> <div class="layers">
{{range .SortLayers}} <div id="{{.LastLayer.Name}}" class="layer">
<div id="{{.Name}}" class="layer"> <h3 class="layer__title" data-toggle-layer="{{.LastLayer.Name}}">{{.LastLayer.Name}}</h3>
<h3 class="layer__title" data-toggle-layer="{{.Name}}">{{.Name}}</h3>
<div>{{.Path}}</div>
<div class="features"> <div class="features">
<ul> <ul>
{{range .Features}} {{ range sortedVulnerabilities $ia}}
<li class="feature"> <li class="feature">
<div class="feature__title"> <div class="feature__title">
<strong>{{ .Name }}</strong> <span>{{ .Version }}</span> - <span class="fa fa-{{if .Status}}check-circle{{else}}exclamation-triangle{{end}}" aria-hidden="true"></span> <strong>{{ .Name }}</strong> <span>{{ .Version }}</span> - <span class="fa fa-exclamation-triangle" aria-hidden="true"></span>
</div> </div>
{{ range .Vulnerabilities}}
<ul class="vulnerabilities"> <ul class="vulnerabilities">
{{range .Vulnerabilities}}
<li class="vulnerability {{ .Severity }}"> <li class="vulnerability {{ .Severity }}">
<a class="vulnerability__title" name="{{ .Name }}"></a> <a class="vulnerability__title" name="{{ .Name }}"></a>
<strong class="name">{{ .Name }}</strong> <strong class="name">{{ .Name }}</strong>
<div>{{ .Description }}</div> <div>{{ .Description }}</div>
<a href="{{ .Link }}" target="blank">Link</a> <a href="{{ .Link }}" target="blank">Link</a>
</li> </li>
{{end}}
</ul> </ul>
</li> </li>
{{end}} {{end}}
{{end}}
</ul> </ul>
</div> </div>
</div> </div>
{{end}}
</div> </div>
</div> </div>
<br /> <br />