Add explanations for each error

first pass

add info links to dashboard
This commit is contained in:
Bobby Brennan
2019-05-08 14:54:31 +00:00
parent 2c1c4c2cca
commit bd2da76c56
11 changed files with 392 additions and 276 deletions

View File

@@ -0,0 +1,12 @@
th, td {
padding: 4px 8px;
vertical-align: top;
}
body {
font-family: "Helvetica", "Arial", sans-serif
}
h1, h2, h3, h4, h5, h6 {
font-family: "Muli", "Helvetica", "Arial", sans-serif
}

View File

@@ -0,0 +1,227 @@
.charts {
height: 405px;
}
.card.cluster {
margin-top: 15px;
}
.card.namespace {
padding: 10px 20px;
}
.card h3 {
margin: 0;
font-weight: 300;
font-size: 28px;
padding: 15px 20px 20px;
}
.namespace h3 strong {
margin: 0;
font-weight: bold;
}
.cluster-overview {
height: 204px;
}
.cluster-overview .cluster-score {
width: 200px;
display: inline-block;
margin-left: 20px;
}
.cluster-overview .cluster-score .weather {
font-size: 90px;
color: #444;
margin-bottom: 15px;
}
.cluster-overview .cluster-score .sailing-message {
font-size: 16px;
line-height: 28px;
margin-left: 7px;
font-weight: 300;
color: #555;
}
.cluster-overview .cluster-score .scores {
font-size: 16px;
margin-top: 10px;
}
.cluster .expandable-table ul.message-list {
margin: 10px 26px;
}
#clusterScoreChart {
width: 550px;
position: relative;
top: -227px;
left: 120px;
}
.cluster-overview .result-messages {
margin: 0 20px;
display: inline-block;
position: relative;
left: -100px;
top: -45px;
}
.cluster-overview .result-messages ul {
position: relative;
top: 0px;
left: 400px;
font-size: 20px;
line-height: 35px;
}
.card.cluster .expandable-table {
margin-top: 20px;
}
.card.cluster .detail-label {
display: inline-block;
min-width: 140px;
font-weight: bold;
}
.expandable-table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
.expandable-table tr {
height: 50px;
}
.expandable-table td {
padding: 15px 20px;
margin: 0;
font-size: 18px;
border-top: 1px solid #eee;
}
.expandable-table .resource-info .name {
cursor: pointer;
}
.expandable-table .resource-info .caret-expander {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 10px;
background-image: url('../images/caret-right.svg');
background-size: 13px auto;
background-repeat: no-repeat;
background-position: 2px center;
}
.expandable-table .resource-info.expanded .caret-expander {
background-image: url('../images/caret-bottom.svg');
background-position: center 2px;
}
.expandable-table .resource-info .expandable-content {
display: none;
}
.expandable-table .resource-info.expanded .expandable-content {
display: block;
}
.namespace .resource-info .result-messages {
color: #6a6a6a;
}
.namespace .result-messages h4 {
font-weight: bold;
font-size: 15px;
margin: 15px 25px 6px;
}
ul.message-list {
list-style-type: none;
font-size: 13px;
line-height: 20px;
margin: 5px 35px;
padding: 0;
color: #6a6a6a;
}
ul.message-list li {
margin-bottom: 5px;
}
ul.message-list li i.message-icon {
display: inline-block;
margin-right: 7px;
text-align: center;
width: 20px;
font-size: 17px;
font-weight: bold;
position: relative;
bottom: -3px;
}
.result-messages .success i.message-icon {
color: #8BD2DC;
}
.result-messages .warning i.message-icon {
color: #f26c21;
}
.result-messages .error i.message-icon {
color: #a11f4c;
}
a.more-info {
color: #6a6a6a;
text-decoration: none;
}
.namespace .status-bar {
vertical-align: top;
padding-top: 18px;
}
.namespace .status-bar .status {
float: right;
animation: fadeIn 2s;
}
.cluster .status {
width: 280px;
}
.namespace .status {
width: 200px;
}
.status div {
height: 15px;
border-radius: 10px;
}
.status .passing {
background-color: #8BD2DC;
float: left;
}
.status .warning {
background-color: #f26c21;
float: left;
}
.status .failing {
background-color: #a11f4c;
width: 100%;
}
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}

View File

@@ -2,50 +2,47 @@ body {
margin: 0;
font-family: 'Muli', 'Helvetica', 'Arial', sans-serif;
background: #f5f5f5;
line-height: 1.3rem;
}
.header {
.navbar {
padding: 20px 20px 0;
}
.header .header-content {
.navbar .navbar-content {
margin: 0 auto;
width: 900px;
}
.header .header-right {
.navbar .navbar-right {
padding: 15px;
float: right;
}
.header .logo {
.navbar .logo {
width: 280px;
}
.header span.oss-text {
.navbar span.oss-text {
color: #23103A;
display: block;
font-size: 11px;
margin: 3px 0;
}
.header a {
.navbar a {
text-decoration: none;
}
.header .ro-logo {
.navbar .ro-logo {
height: 40px;
}
.dashboard-content {
.main-content {
width: 960px;
margin: 0 auto;
}
.charts {
height: 405px;
}
.card {
margin: 25px 20px;
padding: 20px;
@@ -55,225 +52,6 @@ body {
border-radius: 5px;
}
.card.cluster {
margin-top: 15px;
}
.card.namespace {
padding: 10px 20px;
}
.card h3 {
margin: 0;
font-weight: 300;
font-size: 28px;
padding: 15px 20px 20px;
}
.namespace h3 strong {
margin: 0;
font-weight: bold;
}
.cluster-overview {
height: 204px;
}
.cluster-overview .cluster-score {
width: 200px;
display: inline-block;
margin-left: 20px;
}
.cluster-overview .cluster-score .weather {
font-size: 90px;
color: #444;
margin-bottom: 15px;
}
.cluster-overview .cluster-score .sailing-message {
font-size: 16px;
line-height: 28px;
margin-left: 7px;
font-weight: 300;
color: #555;
}
.cluster-overview .cluster-score .scores {
font-size: 16px;
margin-top: 10px;
}
.cluster .expandable-table ul.message-list {
margin: 10px 26px;
}
#clusterScoreChart {
width: 550px;
position: relative;
top: -242px;
left: 120px;
}
.cluster-overview .result-messages {
margin: 0 20px;
display: inline-block;
position: relative;
left: -100px;
top: -45px;
}
.cluster-overview .result-messages ul {
position: relative;
top: 0px;
left: 400px;
font-size: 20px;
line-height: 35px;
}
.card.cluster .expandable-table {
margin-top: 20px;
}
.card.cluster .detail-label {
display: inline-block;
min-width: 140px;
font-weight: bold;
}
.expandable-table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
.expandable-table tr {
height: 50px;
}
.expandable-table td {
padding: 15px 20px;
margin: 0;
font-size: 18px;
border-top: 1px solid #eee;
}
.expandable-table .resource-info .name {
cursor: pointer;
}
.expandable-table .resource-info .caret-expander {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 10px;
background-image: url('../images/caret-right.svg');
background-size: 13px auto;
background-repeat: no-repeat;
background-position: 2px center;
}
.expandable-table .resource-info.expanded .caret-expander {
background-image: url('../images/caret-bottom.svg');
background-position: center 2px;
}
.expandable-table .resource-info .expandable-content {
display: none;
}
.expandable-table .resource-info.expanded .expandable-content {
display: block;
}
.namespace .resource-info .result-messages {
color: #6a6a6a;
}
.namespace .result-messages h4 {
font-weight: bold;
font-size: 15px;
margin: 15px 25px 6px;
}
ul.message-list {
list-style-type: none;
font-size: 13px;
line-height: 20px;
margin: 5px 35px;
padding: 0;
color: #6a6a6a;
}
ul.message-list li {
margin-bottom: 5px;
}
ul.message-list li i {
display: inline-block;
margin-right: 7px;
text-align: center;
width: 20px;
font-size: 17px;
font-weight: bold;
position: relative;
bottom: -3px;
}
.result-messages .success i {
color: #8BD2DC;
}
.result-messages .warning i {
color: #f26c21;
}
.result-messages .error i {
color: #a11f4c;
}
.namespace .status-bar {
vertical-align: top;
padding-top: 18px;
}
.namespace .status-bar .status {
float: right;
animation: fadeIn 2s;
}
.cluster .status {
width: 280px;
}
.namespace .status {
width: 200px;
}
.status div {
height: 15px;
border-radius: 10px;
}
.status .passing {
background-color: #8BD2DC;
float: left;
}
.status .warning {
background-color: #f26c21;
float: left;
}
.status .failing {
background-color: #a11f4c;
width: 100%;
}
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}
.footer {
text-align: center;
padding-top: 10px;
@@ -288,3 +66,4 @@ ul.message-list li i {
.footer a:hover {
text-decoration: underline;
}

View File

@@ -25,24 +25,30 @@ import (
"github.com/reactiveops/fairwinds/pkg/kube"
"github.com/reactiveops/fairwinds/pkg/validator"
"github.com/sirupsen/logrus"
"gitlab.com/golang-commonmark/markdown"
)
const (
// MainTemplateName is the main template
MainTemplateName = "main.gohtml"
// HeaderTemplateName contains the navbar
HeaderTemplateName = "header.gohtml"
// HeadTemplateName contains styles and meta info
HeadTemplateName = "head.gohtml"
// NavbarTemplateName contains the navbar
NavbarTemplateName = "navbar.gohtml"
// PreambleTemplateName contains an empty preamble that can be overridden
PreambleTemplateName = "preamble.gohtml"
// DashboardTemplateName contains the content of the dashboard
DashboardTemplateName = "dashboard.gohtml"
// FooterTemplateName contains the footer
FooterTemplateName = "footer.gohtml"
// CheckDetailsTemplateName is a page for rendering details about a given check
CheckDetailsTemplateName = "check-details.gohtml"
)
var (
templateBox = (*packr.Box)(nil)
assetBox = (*packr.Box)(nil)
markdownBox = (*packr.Box)(nil)
)
// GetAssetBox returns a binary-friendly set of assets packaged from disk
@@ -61,6 +67,14 @@ func GetTemplateBox() *packr.Box {
return templateBox
}
// GetMarkdownBox returns a binary-friendly set of markdown files with error details
func GetMarkdownBox() *packr.Box {
if markdownBox == (*packr.Box)(nil) {
markdownBox = packr.New("Markdown", "../../docs")
}
return markdownBox
}
// TemplateData is passed to the dashboard HTML template
type TemplateData struct {
AuditData validator.AuditData
@@ -77,16 +91,22 @@ func GetBaseTemplate(name string) (*template.Template, error) {
"getGrade": getGrade,
"getScore": getScore,
"getIcon": getIcon,
"getCategoryLink": getCategoryLink,
})
templateBox := GetTemplateBox()
templateFileNames := []string{
DashboardTemplateName,
HeaderTemplateName,
HeadTemplateName,
NavbarTemplateName,
PreambleTemplateName,
FooterTemplateName,
MainTemplateName,
}
return parseTemplateFiles(tmpl, templateFileNames)
}
func parseTemplateFiles(tmpl *template.Template, templateFileNames []string) (*template.Template, error) {
templateBox := GetTemplateBox()
for _, fname := range templateFileNames {
templateFile, err := templateBox.Find(fname)
if err != nil {
@@ -101,6 +121,16 @@ func GetBaseTemplate(name string) (*template.Template, error) {
return tmpl, nil
}
func writeTemplate(tmpl *template.Template, data *TemplateData, w http.ResponseWriter) {
buf := &bytes.Buffer{}
err := tmpl.Execute(buf, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
}
// MainHandler gets template data and renders the dashboard with it.
func MainHandler(w http.ResponseWriter, r *http.Request, auditData validator.AuditData) {
jsonData, err := json.Marshal(auditData)
@@ -120,23 +150,14 @@ func MainHandler(w http.ResponseWriter, r *http.Request, auditData validator.Aud
http.Error(w, "Error getting template data", 500)
return
}
buf := &bytes.Buffer{}
err = tmpl.ExecuteTemplate(buf, "main", templateData)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
buf.WriteTo(w)
writeTemplate(tmpl, &templateData, w)
}
// EndpointHandler gets template data and renders json with it.
func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, kubeResources *kube.ResourceProvider) {
templateData, err := validator.RunAudit(c, kubeResources)
if err != nil {
http.Error(w, "Error Fetching Deployments", 500)
http.Error(w, "Error Fetching Deployments", http.StatusInternalServerError)
return
}
@@ -144,3 +165,31 @@ func EndpointHandler(w http.ResponseWriter, r *http.Request, c conf.Configuratio
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(templateData)
}
// DetailsHandler returns details for a given error type
func DetailsHandler(w http.ResponseWriter, r *http.Request, category string) {
box := GetMarkdownBox()
contents, err := box.Find(category + ".md")
if err != nil {
http.Error(w, "Error details not found for category "+category, http.StatusNotFound)
return
}
md := markdown.New(markdown.XHTMLOutput(true))
detailsHTML := "{{ define \"details\" }}" + md.RenderToString(contents) + "{{ end }}"
templateFileNames := []string{
HeadTemplateName,
NavbarTemplateName,
CheckDetailsTemplateName,
FooterTemplateName,
}
tmpl := template.New("check-details")
tmpl, err = parseTemplateFiles(tmpl, templateFileNames)
if err != nil {
logrus.Printf("Error getting template data %v", err)
http.Error(w, "Error getting template data", 500)
return
}
tmpl.Parse(detailsHTML)
writeTemplate(tmpl, nil, w)
}

View File

@@ -16,6 +16,7 @@ package dashboard
import (
"github.com/reactiveops/fairwinds/pkg/validator"
"strings"
)
func getWarningWidth(counts validator.CountSummary, fullWidth int) uint {
@@ -26,6 +27,10 @@ func getSuccessWidth(counts validator.CountSummary, fullWidth int) uint {
return uint(float64(counts.Successes) / float64(counts.Successes+counts.Warnings+counts.Errors) * float64(fullWidth))
}
func getCategoryLink(category string) string {
return strings.Replace(strings.ToLower(category), " ", "-", -1)
}
func getGrade(rs validator.ResultSummary) string {
score := getScore(rs)
if score >= 97 {

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html>
<head>
{{ template "head" . }}
<link rel="stylesheet" href="/static/css/check-details.css">
</head>
<body>
{{ template "navbar" . }}
<div class="main-content">
<div class="card">
{{ template "details" . }}
</div>
</div>
</body>
</html>

View File

@@ -9,9 +9,9 @@
</div>
<div class="result-messages">
<ul class="message-list">
<li class="success"><i class="fas fa-check"></i> {{ .AuditData.ClusterSummary.Results.Totals.Successes }} checks passed</li>
<li class="warning"><i class="fas fa-exclamation"></i> {{ .AuditData.ClusterSummary.Results.Totals.Warnings }} checks had warnings</li>
<li class="error"><i class="fas fa-times"></i> {{ .AuditData.ClusterSummary.Results.Totals.Errors }} checks had errors</li>
<li class="success"><i class="message-icon fas fa-check"></i> {{ .AuditData.ClusterSummary.Results.Totals.Successes }} checks passed</li>
<li class="warning"><i class="message-icon fas fa-exclamation"></i> {{ .AuditData.ClusterSummary.Results.Totals.Warnings }} checks had warnings</li>
<li class="error"><i class="message-icon fas fa-times"></i> {{ .AuditData.ClusterSummary.Results.Totals.Errors }} checks had errors</li>
</ul>
</div>
<canvas id="clusterScoreChart"></canvas>
@@ -82,7 +82,13 @@
<h4>Pod Spec:</h4>
<ul class="message-list">
{{ range $message := .PodResult.Messages}}
<li class="{{ .Type }}"><i class="{{ getIcon $message }}"></i> {{ .Message }}</li>
<li class="{{ .Type }}">
<i class="message-icon {{ getIcon $message }}"></i>
<a class="more-info" href="/details/{{ getCategoryLink .Category }}">
<i class="far fa-question-circle"></i>
</a>
<span class="message">{{ .Message }}</span>
</li>
{{ end }}
</ul>
</div>
@@ -91,7 +97,13 @@
<h4>Container: {{ .Name }}</h4>
<ul class="message-list">
{{ range $message := .Messages}}
<li class="{{ .Type }}"><i class="{{ getIcon $message }}"></i> {{ .Message }}</li>
<li class="{{ .Type }}">
<i class="message-icon {{ getIcon $message }}"></i>
<a class="more-info" href="/details/{{ getCategoryLink .Category }}">
<i class="far fa-question-circle"></i>
</a>
<span class="message">{{ .Message }}</span>
</li>
{{ end }}
</ul>
</div>

View File

@@ -0,0 +1,14 @@
{{ define "head" }}
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Fairwinds</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/static/favicon-16x16.png" sizes="16x16" />
<link href="https://fonts.googleapis.com/css?family=Muli:300,400,700" rel="stylesheet">
<link rel="stylesheet" href="/static/css/normalize.css">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
{{ end }}

View File

@@ -2,19 +2,8 @@
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Fairwinds</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/static/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="/static/favicon-16x16.png" sizes="16x16" />
<link href="https://fonts.googleapis.com/css?family=Muli:300,400,700" rel="stylesheet">
<link rel="stylesheet" href="/static/css/normalize.css">
<link rel="stylesheet" href="/static/css/main.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
{{ template "head" . }}
<link rel="stylesheet" href="/static/css/dashboard.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/cash/3.0.0-beta.3/cash.min.js"></script>
<script>
@@ -24,8 +13,8 @@
</head>
<body>
{{ template "header" . }}
<div class="dashboard-content">
{{ template "navbar" . }}
<div class="main-content">
{{ template "preamble" . }}
{{ template "dashboard" . }}
</div>

View File

@@ -1,8 +1,10 @@
{{define "header"}}
<div class="header">
<div class="header-content">
<img class="logo" src="/static/images/polaris-logo.png" alt="Polaris" />
<div class="header-right">
{{define "navbar"}}
<div class="navbar">
<div class="navbar-content">
<a href="/">
<img class="logo" src="/static/images/polaris-logo.png" alt="Polaris" />
</a>
<div class="navbar-right">
<a href="https://reactiveops.com?source=fairwinds" target="_blank">
<span class="oss-text">Open Source Project By</span>
<img class="ro-logo" src="/static/images/ro-logo.png" alt="ReactiveOps" />