Skip to content

Commit

Permalink
Add start to graphs and an index page (#32)
Browse files Browse the repository at this point in the history
* Add some graphs for the images view, no real data in them yet

* Add index page with beginner graphs

* Add index view data for images

* switch colors in pie charts

* fix golint with variable bump
  • Loading branch information
Starttoaster authored May 6, 2024
1 parent 3da86f6 commit 3e18369
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 3 deletions.
33 changes: 32 additions & 1 deletion internal/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,16 @@ import (
exposedsecretsview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/exposedsecrets"
imageview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/image"
imagesview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/images"
indexview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/index"
roleview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/role"
rolesview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/roles"
)

// Start starts the webserver
func Start(port string) error {
mux := http.NewServeMux()
mux.HandleFunc("/", imagesHandler)
mux.HandleFunc("/", indexHandler)
mux.HandleFunc("/images", imagesHandler)
mux.HandleFunc("/image", imageHandler)
mux.HandleFunc("/configaudits", configauditsHandler)
mux.HandleFunc("/configaudit", configauditHandler)
Expand All @@ -39,10 +41,39 @@ func Start(port string) error {
mux.HandleFunc("/exposedsecret", exposedsecretHandler)
mux.HandleFunc("/roles", rolesHandler)
mux.HandleFunc("/role", roleHandler)
// TODO just serve the js and css directories in static
// this serves the html templates for no reason
mux.Handle("/static/", http.FileServer(http.FS(content.Static)))
return http.ListenAndServe(fmt.Sprintf(":%s", port), mux)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFS(content.Static, "static/index.html", "static/sidebar.html"))
if tmpl == nil {
log.Logger.Error("encountered error parsing index html template")
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}

// Get vulnerability reports
data, err := kube.GetVulnerabilityReportList()
if err != nil {
log.Logger.Error("error getting VulnerabilityReports", "error", err.Error())
return
}
imageData := imagesview.GetView(data, imagesview.Filters{})

// Get index view
indexData := indexview.GetView(imageData)

err = tmpl.Execute(w, indexData)
if err != nil {
log.Logger.Error("encountered error executing index html template", "error", err)
http.Error(w, "Internal Server Error, check server logs", http.StatusInternalServerError)
return
}
}

func imagesHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFS(content.Static, "static/images.html", "static/sidebar.html"))
if tmpl == nil {
Expand Down
7 changes: 7 additions & 0 deletions internal/web/views/images/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ func GetView(data *v1alpha1.VulnerabilityReportList, filters Filters) View {
FixedVersion: v.FixedVersion,
}

// Fixed version counter for index page
if vuln.FixedVersion != "" {
i[imageIndex].FixAvailableCount++
} else {
i[imageIndex].NoFixAvailableCount++
}

// Filter by hasfix
if filters.HasFix {
if strings.TrimSpace(vuln.FixedVersion) == "" {
Expand Down
4 changes: 4 additions & 0 deletions internal/web/views/images/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ type Data struct {
HighVulnerabilities []Vulnerability
MediumVulnerabilities []Vulnerability
LowVulnerabilities []Vulnerability

// Data counters for charts in the index page
FixAvailableCount int
NoFixAvailableCount int
}

// ResourceMetadata data related to a k8s resource using a vulnerable image
Expand Down
28 changes: 28 additions & 0 deletions internal/web/views/index/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package index

import (
imagesview "github.com/starttoaster/trivy-operator-explorer/internal/web/views/images"
)

// GetView converts some report data to the / view
func GetView(vulnList imagesview.View) View {
var i View

for _, image := range vulnList {
i.CriticalVulnerabilities += len(image.CriticalVulnerabilities)
i.HighVulnerabilities += len(image.HighVulnerabilities)
i.MediumVulnerabilities += len(image.MediumVulnerabilities)
i.LowVulnerabilities += len(image.LowVulnerabilities)

i.FixAvailableCount += image.FixAvailableCount
i.NoFixAvailableCount += image.NoFixAvailableCount

if image.OSEndOfServiceLife != "" {
i.EOSLCount++
} else {
i.NoEOSLCount++
}
}

return i
}
14 changes: 14 additions & 0 deletions internal/web/views/index/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package index

// View contains data for the index page
type View struct {
// Data for image vulnerabilities
CriticalVulnerabilities int
HighVulnerabilities int
MediumVulnerabilities int
LowVulnerabilities int
FixAvailableCount int
NoFixAvailableCount int
EOSLCount int
NoEOSLCount int
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import (
//go:embed static/exposedsecret.html
//go:embed static/images.html
//go:embed static/image.html
//go:embed static/index.html
//go:embed static/css/output.css
//go:embed static/css/extra.css
//go:embed static/js/chart.js
var static embed.FS

func main() {
Expand Down
20 changes: 20 additions & 0 deletions static/css/extra.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,23 @@

/* Show the dropdown menu on hover */
.dropdown:hover .dropdown-content {display: block;}

#images-wrapper {
display: flex;
justify-content: space-between;
}
#c1 {
width: 45%;
display: inline-block;
margin: 10px;
}
#c2 {
width: 20%;
display: inline-block;
margin: 10px;
}
#c3 {
width: 20%;
display: inline-block;
margin: 10px;
}
36 changes: 36 additions & 0 deletions static/css/output.css
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,14 @@ video {
--tw-contain-style: ;
}

.visible {
visibility: visible;
}

.static {
position: static;
}

.fixed {
position: fixed;
}
Expand Down Expand Up @@ -610,6 +618,10 @@ video {
display: table;
}

.grid {
display: grid;
}

.hidden {
display: none;
}
Expand Down Expand Up @@ -659,6 +671,10 @@ video {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}

.resize {
resize: both;
}

.flex-col {
flex-direction: column;
}
Expand Down Expand Up @@ -922,6 +938,10 @@ video {
text-transform: uppercase;
}

.italic {
font-style: italic;
}

.text-black {
--tw-text-opacity: 1;
color: rgb(0 0 0 / var(--tw-text-opacity));
Expand Down Expand Up @@ -972,12 +992,28 @@ video {
color: rgb(133 77 14 / var(--tw-text-opacity));
}

.underline {
text-decoration-line: underline;
}

.shadow-md {
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}

.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}

.transition {
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
transition-duration: 150ms;
}

.transition-transform {
transition-property: transform;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
Expand Down
3 changes: 2 additions & 1 deletion static/images.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/css/output.css" rel="stylesheet">
<link href="/static/css/extra.css" rel="stylesheet">
<script src="/static/js/chart.js"></script>
</head>
<body class="min-h-screen bg-gray-200 dark:bg-indigo-900">

Expand All @@ -13,7 +14,7 @@

<!-- Table content -->
<div class="p-4 sm:ml-64 bg-gray-200 dark:bg-indigo-900">
<div class="relative overflow-x-auto shadow-md rounded-lg">
<div class="p-2 relative overflow-x-auto shadow-md rounded-lg">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<!-- Table headers -->
<thead class="rounded-lg text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
Expand Down
120 changes: 120 additions & 0 deletions static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="en">
<title>Explorer: Images</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/css/output.css" rel="stylesheet">
<link href="/static/css/extra.css" rel="stylesheet">
<script src="/static/js/chart.js"></script>
</head>
<body class="min-h-screen bg-gray-200 dark:bg-indigo-900">

<!-- Sidebar -->
{{template "sidebar.html"}}

<div class="p-4 sm:ml-64 bg-gray-200 dark:bg-indigo-900">
<!-- Images content -->
<div class="p-4 relative overflow-x-auto shadow-md rounded-lg bg-gray-50 dark:bg-gray-800">
<div class="w-full text-xl text-center text-black dark:text-white">
<a href="/images">
Image Vulnerabilities
</a>
</div>

<div id="images-wrapper">
<div id="c1"><canvas id="vulnChart"></canvas></div>
<div id="c2"><canvas id="eoslChart"></canvas></div>
<div id="c3"><canvas id="fixedChart"></canvas></div>
</div>

<script defer>
Chart.defaults.font.size = 16;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
Chart.defaults.color = '#fff';
} else {
Chart.defaults.color = '#000';
}
new Chart(document.getElementById('vulnChart'), {
type: 'bar',
data: {
labels: ['Critical', 'High', 'Medium', 'Low'],
datasets: [{
label: 'Severity',
data: [{{ .CriticalVulnerabilities }}, {{ .HighVulnerabilities }}, {{ .MediumVulnerabilities }}, {{ .LowVulnerabilities }}],
backgroundColor: [
'rgba(255, 0, 0, 1)',
'rgba(255, 125, 20, 1)',
'rgba(255, 255, 0, 1)',
'rgba(30, 30, 210, 1)'
],
}]
},
options: {
indexAxis: 'y',
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>

<script defer>
Chart.defaults.font.size = 16;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
Chart.defaults.color = '#fff';
} else {
Chart.defaults.color = '#000';
}
new Chart(document.getElementById('eoslChart'), {
type: 'pie',
data: {
labels: ['Not EoSL', 'EoSL'],
datasets: [{
label: 'End of Service Life images',
data: [{{ .NoEOSLCount }}, {{ .EOSLCount }}],
backgroundColor: [
'rgba(39, 245, 127, 1)',
'rgba(249, 51, 51, 1)'
],
}]
},
options: {
borderWidth: 0,
responsive: true
}
});
</script>

<script defer>
Chart.defaults.font.size = 16;
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
Chart.defaults.color = '#fff';
} else {
Chart.defaults.color = '#000';
}
new Chart(document.getElementById('fixedChart'), {
type: 'pie',
data: {
labels: ['Has fix', 'No fix'],
datasets: [{
label: 'Fix Available',
data: [{{ .FixAvailableCount }}, {{ .NoFixAvailableCount }}],
backgroundColor: [
'rgba(39, 245, 127, 1)',
'rgba(249, 51, 51, 1)'
],
}]
},
options: {
borderWidth: 0,
responsive: true
}
});
</script>
</div>
</div>
</body>
</html>
Loading

0 comments on commit 3e18369

Please sign in to comment.