diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go index bc98d94c..1ea79030 100644 --- a/pkg/dashboard/dashboard.go +++ b/pkg/dashboard/dashboard.go @@ -17,7 +17,7 @@ type DashboardData struct { NamespacedResults map[string]*validator.NamespacedResult } -var tmpl = template.Must(template.ParseFiles("pkg/dashboard/templates/charts.gohtml")) +var tmpl = template.Must(template.ParseFiles("pkg/dashboard/templates/dashboard.gohtml")) func Render(w http.ResponseWriter, r *http.Request, c conf.Configuration) { dashboardData, err := getDashboardData(c) @@ -65,7 +65,7 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { for _, deploy := range deploys.Items { validationFailures := validator.ValidateDeploys(c, &deploy) - resResult := validator.ResourceResult{ + deployResult := validator.ResourceResult{ Name: deploy.Name, Type: "Deployment", Summary: &validator.ResultSummary{ @@ -73,6 +73,7 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { Warnings: 2, Failures: 0, }, + ContainerResults: []validator.ContainerResult{}, } if dashboardData.NamespacedResults[deploy.Namespace] == nil { @@ -88,24 +89,27 @@ func getDashboardData(c conf.Configuration) (DashboardData, error) { containerValidations := append(validationFailures.InitContainerValidations, validationFailures.ContainerValidations...) for _, containerValidation := range containerValidations { + containerResult := validator.ContainerResult{ + Name: containerValidation.Container.Name, + Messages: []validator.ResultMessage{}, + } for _, success := range containerValidation.Successes { dashboardData.ClusterSummary.Successes++ dashboardData.NamespacedResults[deploy.Namespace].Summary.Successes++ - resResult.Summary.Successes++ - resResult.Messages = append(resResult.Messages, success) + deployResult.Summary.Successes++ + containerResult.Messages = append(containerResult.Messages, success) } for _, failure := range containerValidation.Failures { dashboardData.ClusterSummary.Failures++ dashboardData.NamespacedResults[deploy.Namespace].Summary.Failures++ - resResult.Summary.Failures++ - resResult.Messages = append(resResult.Messages, validator.ResultMessage{ - Message: failure.Reason(), - Type: "failure", - }) + deployResult.Summary.Failures++ + containerResult.Messages = append(containerResult.Messages, failure) } + deployResult.ContainerResults = append(deployResult.ContainerResults, containerResult) } - dashboardData.NamespacedResults[deploy.Namespace].Results = append(dashboardData.NamespacedResults[deploy.Namespace].Results, resResult) + dashboardData.NamespacedResults[deploy.Namespace].Results = append( + dashboardData.NamespacedResults[deploy.Namespace].Results, deployResult) } return dashboardData, nil diff --git a/pkg/dashboard/templates/charts.gohtml b/pkg/dashboard/templates/charts.gohtml deleted file mode 100644 index 24e60875..00000000 --- a/pkg/dashboard/templates/charts.gohtml +++ /dev/null @@ -1,157 +0,0 @@ - - - - - - - Fairwinds - - - - - - - - - - - - - - -
-

- - -

-
- -
-
-
-

Overall Score:

-
- -
-
-
-

Scores By Namespace:

- -
-
- - {{ range $namespace, $results := .NamespacedResults }} -
-

Namespace: {{ $namespace }}

- - - {{ range $results.Results }} - - - - - {{ end }} -
-
{{ .Type }}: {{ .Name }}
-
    - {{ range .Messages}} -
  • &#{{ .HTMLSpecialCharCode }}; {{ .Message }}
  • - {{ end }} -
-
-
-
-
-
-
-
-
-
-
- {{ end }} -
- - - - - diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml index 6a6d73a1..d51cf518 100644 --- a/pkg/dashboard/templates/dashboard.gohtml +++ b/pkg/dashboard/templates/dashboard.gohtml @@ -11,68 +11,152 @@ + + + + +
-

Fairwinds

+

+ + +

-
-

Overall Score:

-

{{ .ClusterSummary.Score }}%

-
-
-
-
+
+
+
+

Overall Score:

+
+
-
-
-
- Successes: - {{ .ClusterSummary.Successes }} -
-
- Warnings: - {{ .ClusterSummary.Warnings }} -
-
- Failures: - {{ .ClusterSummary.Failures }} +
+

Scores By Namespace:

+
-
-{{ range .NamespacedResults }} -
-

Namespace: {{ .Namespace }}

-
+ {{ range $namespace, $results := .NamespacedResults }} +
+

Namespace: {{ $namespace }}

- - {{ range .Results }} - - - - +
-
{{ .Type }}: {{ .Name }}
-
    - {{ range .Messages}} -
  • &#{{ .HTMLSpecialCharCode }}; {{ .Message }}
  • - {{ end }} -
-
-
-
-
-
-
-
-
-
+ {{ range $results.Results }} + + + + + {{ end }} +
+
{{ .Type }}: {{ .Name }}
+ {{ range .ContainerResults}} +
+

{{ .Name }}

+
    + {{ range .Messages}} +
  • &#{{ .HTMLSpecialCharCode }}; {{ .Message }}
  • + {{ end }} +
+
+ {{ end }} +
+
+
+
+
+
+
+
+
+
{{ end }} - -{{ end }} +
+ + diff --git a/pkg/types/types.go b/pkg/types/types.go deleted file mode 100644 index e2c23d00..00000000 --- a/pkg/types/types.go +++ /dev/null @@ -1,21 +0,0 @@ -package types - -import ( - "fmt" -) - -// Failure contains information about the failing validation. -type Failure struct { - Name string - Expected string - Actual string -} - -// Reason returns a string that describes the reason for a Failure. -func (f *Failure) Reason() string { - return fmt.Sprintf("%s: Expected: %s, Actual: %s.", - f.Name, - f.Expected, - f.Actual, - ) -} diff --git a/pkg/validator/container.go b/pkg/validator/container.go index b8acd58e..5760eeb2 100644 --- a/pkg/validator/container.go +++ b/pkg/validator/container.go @@ -19,7 +19,6 @@ import ( "strings" conf "github.com/reactiveops/fairwinds/pkg/config" - "github.com/reactiveops/fairwinds/pkg/types" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" ) @@ -27,7 +26,7 @@ import ( // ContainerValidation tracks validation failures associated with a Container type ContainerValidation struct { Container corev1.Container - Failures []types.Failure + Failures []ResultMessage Successes []ResultMessage } @@ -43,11 +42,10 @@ func validateContainer(conf conf.Configuration, container corev1.Container) Cont return cv } -func (cv *ContainerValidation) addFailure(name, expected, actual string) { - cv.Failures = append(cv.Failures, types.Failure{ - Name: name, - Expected: expected, - Actual: actual, +func (cv *ContainerValidation) addFailure(message string) { + cv.Failures = append(cv.Failures, ResultMessage{ + Message: message, + Type: "failure", }) } @@ -60,28 +58,28 @@ func (cv *ContainerValidation) addSuccess(message string) { func (cv *ContainerValidation) validateResources(conf conf.RequestsAndLimits) { actualRes := cv.Container.Resources - cv.withinRange("requests.cpu", conf.Requests["cpu"], actualRes.Requests.Cpu()) - cv.withinRange("requests.memory", conf.Requests["memory"], actualRes.Requests.Memory()) - cv.withinRange("limits.cpu", conf.Limits["cpu"], actualRes.Limits.Cpu()) - cv.withinRange("limits.memory", conf.Limits["memory"], actualRes.Limits.Memory()) + cv.withinRange("CPU Requests", conf.Requests["cpu"], actualRes.Requests.Cpu()) + cv.withinRange("Memory Requests", conf.Requests["memory"], actualRes.Requests.Memory()) + cv.withinRange("CPU Limits", conf.Limits["cpu"], actualRes.Limits.Cpu()) + cv.withinRange("Memory Limits", conf.Limits["memory"], actualRes.Limits.Memory()) } func (cv *ContainerValidation) withinRange(resourceName string, expectedRange conf.ResourceMinMax, actual *resource.Quantity) { expectedMin := expectedRange.Min expectedMax := expectedRange.Max if expectedMin != nil && expectedMin.MilliValue() > actual.MilliValue() { - cv.addFailure(resourceName, expectedMin.String(), actual.String()) + cv.addFailure(fmt.Sprintf("%s are too low", resourceName)) } else if expectedMax != nil && expectedMax.MilliValue() < actual.MilliValue() { - cv.addFailure(resourceName, expectedMax.String(), actual.String()) + cv.addFailure(fmt.Sprintf("%s are too high", resourceName)) } else { - cv.addSuccess(fmt.Sprintf("Resource %s within expected range", resourceName)) + cv.addSuccess(fmt.Sprintf("%s are within the expected range", resourceName)) } } func (cv *ContainerValidation) validateHealthChecks(conf conf.Probes) { if conf.Readiness.Require { if cv.Container.ReadinessProbe == nil { - cv.addFailure("readiness", "probe needs to be configured", "nil") + cv.addFailure("Readiness probe needs to be configured") } else { cv.addSuccess("Readiness probe configured") } @@ -89,7 +87,7 @@ func (cv *ContainerValidation) validateHealthChecks(conf conf.Probes) { if conf.Liveness.Require { if cv.Container.LivenessProbe == nil { - cv.addFailure("liveness", "probe needs to be configured", "nil") + cv.addFailure("Liveness probe needs to be configured") } else { cv.addSuccess("Liveness probe configured") } @@ -100,7 +98,7 @@ func (cv *ContainerValidation) validateImage(conf conf.Images) { if conf.TagRequired { img := strings.Split(cv.Container.Image, ":") if len(img) == 1 || img[1] == "latest" { - cv.addFailure("Image Tag", "not latest", "latest") + cv.addFailure("Image tag should be specified") } else { cv.addSuccess("Image tag specified") } diff --git a/pkg/validator/report.go b/pkg/validator/report.go index 8ffe502c..ef716061 100644 --- a/pkg/validator/report.go +++ b/pkg/validator/report.go @@ -42,7 +42,7 @@ func validContainer(sb *strings.Builder, cv *ContainerValidation) bool { s := fmt.Sprintf("\nContainer: %s\n Failure/s:\n", cv.Container.Name) sb.WriteString(s) for _, failure := range cv.Failures { - sb.WriteString(fmt.Sprintf("- %s\n", failure.Reason())) + sb.WriteString(fmt.Sprintf("- %s\n", failure.Message)) } return false } diff --git a/pkg/validator/types.go b/pkg/validator/types.go index deb12f2c..790a2279 100644 --- a/pkg/validator/types.go +++ b/pkg/validator/types.go @@ -6,10 +6,10 @@ type NamespacedResult struct { } type ResourceResult struct { - Name string - Type string - Summary *ResultSummary - Messages []ResultMessage + Name string + Type string + Summary *ResultSummary + ContainerResults []ContainerResult } type ResultSummary struct { @@ -18,6 +18,11 @@ type ResultSummary struct { Failures uint } +type ContainerResult struct { + Name string + Messages []ResultMessage +} + type ResultMessage struct { Message string Type string diff --git a/public/charts.html b/public/charts.html deleted file mode 100644 index fd18c33b..00000000 --- a/public/charts.html +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - Fairwinds - - - - - - - - - - - -
-

- - -

-
- -
-
-
-

Overall Score:

-
- -
-
-
-

Scores By Namespace:

- -
-
- -
-

Namespace: kube-system

- - - - - - - - - - -
-
DaemonSet: datadog-agent
-
-
-
-
-
-
-
-
-
-
Deployment: tiller-deployment
-
    -
  • Image Tag Specified
  • -
  • Resource Limits
  • -
  • Resource Requests
  • -
  • Run As Non Root User
  • -
  • Liveness Probe
  • -
  • Readiness Probe
  • -
-
-
-
-
-
-
-
-
-
-
-
- - - - - \ No newline at end of file diff --git a/public/css/charts.css b/public/css/charts.css deleted file mode 100644 index 3c92b2cb..00000000 --- a/public/css/charts.css +++ /dev/null @@ -1,210 +0,0 @@ -body { - margin: 0; - font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; - background: #eee; -} - -.header { - background-color: #fff; - color: #006369; - padding: 10px 20px; - box-shadow: 0 2px 5px rgba(0,0,0,0.3); -} - -.header h1 { - margin: 0 auto; - width: 900px; -} - -.header h1 .logo { - width: 280px; -} - -.header h1 span { - font-family: 'Lobster', sans-serif; - font-size: 60px; - font-weight: normal; - display: none; -} - -.dashboard-content { - width: 960px; - margin: 0 auto; -} - -.charts { - height: 400px; -} - -.chart-section { - float: left; - margin: 20px; - padding: 30px; - width: 300px; - height: 320px; - color: #333; - background-color: #fff; - box-shadow: 0 2px 5px rgba(0,0,0,0.3); - border-radius: 5px; -} - -.chart-section.namespace-score { - margin-left: 0px; - width: 480px; -} - -.chart-section h3 { - margin: 0 0 15px; - padding: 0 0 15px; - font-weight: 300; - font-size: 30px; - border-bottom: 1px solid #eee; -} - -#clusterScoreChart { - width: 260px; - height: 260px; -} - -#namespaceScoreChart { - width: 520px; - height: 260px; -} - -.namespace { - margin: 20px; - padding: 10px 20px; - color: #333; - background-color: #fff; - box-shadow: 0 2px 5px rgba(0,0,0,0.3); - border-radius: 5px; -} - -.namespace h3 { - margin: 0; - font-weight: 300; - font-size: 30px; - padding: 20px; -} - -.namespace h3 strong { - margin: 0; - font-weight: bold; - font-size: 30px; -} - -.cluster-score .status { - width: 400px; - height: 30px; - margin: 30px auto; -} - -.namespace-content { - width: 100%; - border-spacing: 0; - border-collapse: collapse; -} - -.namespace-content tr { - height: 50px; -} - -.namespace-content td { - padding: 15px 20px; - margin: 0; - font-size: 18px; - border-top: 1px solid #eee; -} - -.namespace-content .resource-info .name { - cursor: pointer; -} - -.namespace-content .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; -} - -.namespace-content .resource-info.expanded .caret-expander { - background-image: url('../images/caret-bottom.svg'); - background-position: center 2px; -} - -.namespace-content .resource-info .extra { - list-style-type: none; - font-size: 14px; - line-height: 19px; - margin: 10px 30px; - padding: 0; - display: none; -} - -.namespace-content .resource-info.expanded .extra { - display: block; -} - -.namespace-content .extra span { - display: inline-block; - text-align: center; - width: 20px; - font-size: 20px; -} - -.namespace-content .extra .success { - color: #230f39; -} - -.namespace-content .extra .warning { - color: #f26c21; -} - -.namespace-content .extra .warning span { - font-size: 15px; -} - -.namespace-content .extra .failure { - color: #a11f4c; -} - -.namespace-content td.status-bar { - vertical-align: top; - padding-top: 18px; -} - -.namespace-content .status { - float: right; - width: 200px; -} - -.namespace-content .status div { - height: 15px; - border-radius: 10px; -} - -.namespace-content .status .passing { - background-color: #230f39; - float: left; -} - -.namespace-content .status .warning { - background-color: #f26c21; - float: left; -} - -@keyframes fadeIn { - 0% {opacity: 0;} - 100% {opacity: 1;} -} - -.namespace-content .status .failing { - background-color: #a11f4c; - width: 200px; - animation: fadeIn 2s; -} - diff --git a/public/css/main.css b/public/css/main.css index d587aa48..6095dc48 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1,96 +1,92 @@ body { margin: 0; - font-family: 'Helvetica', 'Arial', sans-serif; + font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + background: #eee; } .header { - background-color: #006369; - color: #fff; - padding: 50px; + background-color: #fff; + padding: 10px 20px; + box-shadow: 0 2px 5px rgba(0,0,0,0.3); } .header h1 { + margin: 0 auto; + width: 900px; +} + +.header h1 .logo { + width: 280px; +} + +.header h1 span { font-family: 'Lobster', sans-serif; - font-size: 100px; + font-size: 60px; font-weight: normal; - text-align: center; - margin: 0; + display: none; } -.cluster-score { - margin: 0; +.dashboard-content { + width: 960px; + margin: 0 auto; +} + +.charts { + height: 400px; +} + +.chart-section { + float: left; + margin: 20px; padding: 30px; - color: #003339; - background-color: #eaf3f9; - text-align: center; -} - -.cluster-score h2 { - margin: 0; - font-size: 80px; -} - -.cluster-score .status { - width: 400px; - height: 30px; - margin: 30px auto; -} - -.cluster-score .status div { - height: 30px; - border-radius: 8px; -} - -.cluster-score .status .passing { - background-color: #4ab977; - float: left; -} - -.cluster-score .status .warning { - background-color: #bdbd63; - float: left; -} - -.cluster-score .status .failing { - background-color: #c37575; - width: 400px; -} - -.cluster-score p { - margin: 10px 0; - font-weight: 300; - font-size: 24px; -} - -.cluster-score .summary { - margin: 30px auto; - font-weight: 300; - font-size: 24px; width: 300px; - text-align: left; - line-height: 40px; + height: 320px; + color: #333; + background-color: #fff; + box-shadow: 0 2px 5px rgba(0,0,0,0.3); + border-radius: 5px; } -.cluster-score .summary .value { - float: right; - font-size: 28px; +.chart-section.namespace-score { + margin-left: 0px; + width: 480px; } -.namespace-header { - background-color: #006369; - color: #fff; - font-size: 50px; - font-weight: normal; - padding: 20px; +.chart-section h3 { + margin: 0 0 15px; + padding: 0 0 15px; + font-weight: 300; + font-size: 30px; + border-bottom: 1px solid #eee; } -.namespace-header h3 { +#clusterScoreChart { + width: 260px; + height: 260px; +} + +#namespaceScoreChart { + width: 520px; + height: 260px; +} + +.namespace { + margin: 20px; + padding: 10px 20px; + color: #333; + background-color: #fff; + box-shadow: 0 2px 5px rgba(0,0,0,0.3); + border-radius: 5px; +} + +.namespace h3 { margin: 0; font-weight: 300; font-size: 30px; + padding: 20px; } -.namespace-header h3 strong { +.namespace h3 strong { margin: 0; font-weight: bold; font-size: 30px; @@ -104,7 +100,6 @@ body { .namespace-content { width: 100%; - background-color: #eaf3f9; border-spacing: 0; border-collapse: collapse; } @@ -117,53 +112,74 @@ body { padding: 15px 20px; margin: 0; font-size: 18px; - border-bottom: 1px solid #aac3c9; + border-top: 1px solid #eee; } -.namespace-content .caret-expander { +.namespace-content .resource-info .name { + cursor: pointer; +} + +.namespace-content .resource-info .caret-expander { display: inline-block; width: 15px; height: 15px; margin-right: 10px; background-image: url('../images/caret-right.svg'); - background-size: 15px auto; + background-size: 13px auto; background-repeat: no-repeat; background-position: 2px center; } -.namespace-content .caret-expander.expanded { +.namespace-content .resource-info.expanded .caret-expander { background-image: url('../images/caret-bottom.svg'); - background-position: center center; + background-position: center 2px; } -.namespace-content .extra { +.namespace-content .resource-info .extra { + display: none; + color: #6a6a6a; +} + +.namespace-content .resource-info.expanded .extra { + display: block; +} + +.namespace-content .extra h4 { + font-weight: bold; + font-size: 15px; + margin: 12px 25px 5px; +} + +.namespace-content .extra ul { list-style-type: none; - font-size: 14px; - line-height: 19px; - margin: 10px 30px; + font-size: 13px; + line-height: 20px; + margin: 5px 35px; padding: 0; + color: #6a6a6a; } .namespace-content .extra span { display: inline-block; text-align: center; width: 20px; + font-size: 20px; + font-weight: bold; + position: relative; + bottom: -1px; } -.namespace-content .extra .success { - color: #2a9957; -} - -.namespace-content .extra .warning { - color: #adad43; +.namespace-content .extra .success span { + color: #8BD2DC; } .namespace-content .extra .warning span { - font-size: 11px; + color: #f26c21; + font-size: 15px; } -.namespace-content .extra .failure { - color: #a35555; +.namespace-content .extra .failure span { + color: #a11f4c; } .namespace-content td.status-bar { @@ -178,21 +194,27 @@ body { .namespace-content .status div { height: 15px; - border-radius: 4px; + border-radius: 10px; } .namespace-content .status .passing { - background-color: #4ab977; + background-color: #8BD2DC; float: left; } .namespace-content .status .warning { - background-color: #bdbd63; + background-color: #f26c21; float: left; } -.namespace-content .status .failing { - background-color: #c37575; - width: 200px; +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.namespace-content .status .failing { + background-color: #a11f4c; + width: 200px; + animation: fadeIn 2s; } diff --git a/public/images/logo.png b/public/images/logo.png index 57bdb0a9..cda27161 100644 Binary files a/public/images/logo.png and b/public/images/logo.png differ diff --git a/public/index.html b/public/index.html index 97ac32ef..1a27bbc7 100644 --- a/public/index.html +++ b/public/index.html @@ -11,82 +11,185 @@ + +
-

Fairwinds

+

+ + +

-
-

Overall Score:

-

64%

-
-
-
-
+
+
+
+

Overall Score:

+
+
+
+

Scores By Namespace:

+ +
-
-
- Successes: - 118 -
-
- Warnings: - 34 -
-
- Failures: - 28 -
+ +
+

Namespace: kube-system

+ + + + + + + + + + +
+
DaemonSet: datadog-agent
+
+
+
+
+
+
+
+
+
+
Deployment: tiller-deployment
+
    +
  • Image Tag Specified
  • +
  • Resource Limits
  • +
  • Resource Requests
  • +
  • Run As Non Root User
  • +
  • Liveness Probe
  • +
  • Readiness Probe
  • +
+
+
+
+
+
+
+
+
+
-
-

Namespace: kube-system

-
+ - + \ No newline at end of file