From d363eed95fdef41fee8aee3e83c76c79e221a198 Mon Sep 17 00:00:00 2001 From: Rob Scott Date: Mon, 15 Apr 2019 11:15:55 -0400 Subject: [PATCH 1/3] some dashboard cleanup --- pkg/dashboard/templates/dashboard.gohtml | 12 +- public/css/main.css | 44 ++--- public/images/circle-check.svg | 3 - public/images/circle-x.svg | 3 - public/images/warning.svg | 3 - public/index.html | 195 ----------------------- public/js/charts.js | 28 ++-- 7 files changed, 44 insertions(+), 244 deletions(-) delete mode 100755 public/images/circle-check.svg delete mode 100755 public/images/circle-x.svg delete mode 100755 public/images/warning.svg delete mode 100644 public/index.html diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml index 2d2635b3..f60cb7c1 100644 --- a/pkg/dashboard/templates/dashboard.gohtml +++ b/pkg/dashboard/templates/dashboard.gohtml @@ -8,7 +8,7 @@ - + @@ -27,8 +27,8 @@
@@ -38,13 +38,13 @@
-

Overall Score:

+

Overall Score

-

Scores By Namespace:

+

Scores By Namespace

@@ -96,7 +96,7 @@
- - - -
-

- - -

-
- -
-
-
-

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/js/charts.js b/public/js/charts.js index b616174b..a11e7076 100644 --- a/public/js/charts.js +++ b/public/js/charts.js @@ -1,6 +1,6 @@ if (!Object.values) { - Object.values = function(obj) { - return Object.keys(obj).map(function(key) { + Object.values = function (obj) { + return Object.keys(obj).map(function (key) { return obj[key]; }) } @@ -14,17 +14,17 @@ $(function () { datasets: [{ label: 'Passing', data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function(r) { return r.Summary.Successes}), + .map(function (r) { return r.Summary.Successes }), backgroundColor: '#8BD2DC', - },{ + }, { label: 'Warning', data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function(r) { return r.Summary.Warnings}), + .map(function (r) { return r.Summary.Warnings }), backgroundColor: '#f26c21', - },{ + }, { label: 'Failing', data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function(r) { return r.Summary.Errors}), + .map(function (r) { return r.Summary.Errors }), backgroundColor: '#a11f4c', }] }, @@ -47,9 +47,9 @@ $(function () { }); var score = fairwindsAuditData.ClusterSummary.Successes / ( - fairwindsAuditData.ClusterSummary.Successes + - fairwindsAuditData.ClusterSummary.Warnings + - fairwindsAuditData.ClusterSummary.Errors); + fairwindsAuditData.ClusterSummary.Successes + + fairwindsAuditData.ClusterSummary.Warnings + + fairwindsAuditData.ClusterSummary.Errors); score = Math.round(score * 100); var clusterChart = new Chart("clusterScoreChart", { @@ -62,7 +62,7 @@ $(function () { fairwindsAuditData.ClusterSummary.Warnings, fairwindsAuditData.ClusterSummary.Errors, ], - backgroundColor: ['#8BD2DC','#f26c21','#a11f4c'], + backgroundColor: ['#8BD2DC', '#f26c21', '#a11f4c'], }] }, options: { @@ -73,10 +73,10 @@ $(function () { }, elements: { center: { - text: score + '%', + text: score, color: '#333', //Default black - fontStyle: 'Helvetica', //Default Arial - sidePadding: 30 //Default 20 (as a percentage) + fontStyle: 'Muli', //Default Arial + sidePadding: 40, //Default 20 (as a percentage) } } } From 6f06a5ef270cf7da99721fd58c0996e9ccea6199 Mon Sep 17 00:00:00 2001 From: Rob Scott Date: Mon, 15 Apr 2019 17:59:41 -0400 Subject: [PATCH 2/3] added weighted score + letter grades + lots of related dashboard updates --- pkg/dashboard/dashboard.go | 35 ++++++ pkg/dashboard/templates/dashboard.gohtml | 27 +++-- public/css/main.css | 135 ++++++++++++----------- public/js/charts.js | 67 +---------- public/js/main.js | 52 ++------- 5 files changed, 135 insertions(+), 181 deletions(-) diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go index 482b4c60..17b27090 100644 --- a/pkg/dashboard/dashboard.go +++ b/pkg/dashboard/dashboard.go @@ -50,6 +50,41 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, k "getSuccessWidth": func(rs validator.ResultSummary, fullWidth int) uint { return uint(float64(rs.Successes) / float64(rs.Successes+rs.Warnings+rs.Errors) * float64(fullWidth)) }, + "getGrade": func(rs validator.ResultSummary) string { + total := (rs.Successes * 2) + rs.Warnings + (rs.Errors * 2) + score := uint((float64(rs.Successes*2) / float64(total)) * 100) + if score >= 97 { + return "A+" + } else if score >= 93 { + return "A" + } else if score >= 90 { + return "A-" + } else if score >= 87 { + return "B+" + } else if score >= 83 { + return "B" + } else if score >= 80 { + return "B-" + } else if score >= 77 { + return "C+" + } else if score >= 73 { + return "C" + } else if score >= 70 { + return "C-" + } else if score >= 67 { + return "D+" + } else if score >= 63 { + return "D" + } else if score >= 60 { + return "D-" + } else { + return "F" + } + }, + "getScore": func(rs validator.ResultSummary) uint { + total := (rs.Successes * 2) + rs.Warnings + (rs.Errors * 2) + return uint((float64(rs.Successes*2) / float64(total)) * 100) + }, "getIcon": func(rm validator.ResultMessage) string { switch rm.Type { case "success": diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml index f60cb7c1..54de76bd 100644 --- a/pkg/dashboard/templates/dashboard.gohtml +++ b/pkg/dashboard/templates/dashboard.gohtml @@ -36,16 +36,21 @@
-
-
-

Overall Score

-
- +
+

Cluster Overview

+
+
+
{{ getGrade .AuditData.ClusterSummary }}
+
Overall Score: {{ getScore .AuditData.ClusterSummary }}%
-
-
-

Scores By Namespace

- +
+
    +
  • {{ .AuditData.ClusterSummary.Successes }} checks passed
  • +
  • {{ .AuditData.ClusterSummary.Warnings }} checks had warnings
  • +
  • {{ .AuditData.ClusterSummary.Errors }} checks had errors
  • +
+
+
@@ -59,7 +64,7 @@
{{ .Type }}: {{ .Name }}
{{ range .PodResults}} -
+

Pod Spec:

    {{ range $message := .Messages}} @@ -68,7 +73,7 @@
{{ range .ContainerResults}} -
+

Container: {{ .Name }}

    {{ range $message := .Messages}} diff --git a/public/css/main.css b/public/css/main.css index 6bc02b90..1fa6fa2b 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -47,41 +47,6 @@ body { height: 405px; } -.chart-section { - float: left; - margin: 25px 20px; - padding: 30px; - width: 300px; - height: 320px; - color: #333; - background-color: #fff; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); - 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: 28px; - text-align: center; -} - -#clusterScoreChart { - width: 260px; - height: 260px; -} - -#namespaceScoreChart { - width: 520px; - height: 260px; -} - .namespace { margin: 25px 20px; padding: 10px 20px; @@ -101,13 +66,57 @@ body { .namespace h3 strong { margin: 0; font-weight: bold; - font-size: 30px; } -.cluster-score .status { - width: 400px; - height: 30px; - margin: 30px auto; +.namespace .cluster-overview { + height: 240px; +} + +.namespace .cluster-overview .cluster-score { + width: 200px; + display: inline-block; + margin-left: 20px; +} + +.namespace .cluster-overview .cluster-score .grade { + font-size: 120px; + font-weight: bold; +} + +.namespace .cluster-overview .cluster-score .score { + font-size: 16px; + margin-left: 7px; + font-weight: 300; + color: #555; +} + +.namespace .cluster-overview .cluster-score .score strong { + font-size: 18px; + font-weight: bold; +} + + +#clusterScoreChart { + width: 550px; + position: relative; + top: -220px; + left: 120px; +} + +.namespace .cluster-overview .result-messages { + margin: 0 20px; + display: inline-block; + position: relative; + left: -100px; + top: -15px; +} + +.namespace .cluster-overview .result-messages ul { + position: relative; + top: 0px; + left: 400px; + font-size: 20px; + line-height: 35px; } .namespace-content { @@ -127,11 +136,11 @@ body { border-top: 1px solid #eee; } -.namespace-content .resource-info .name { +.namespace .resource-info .name { cursor: pointer; } -.namespace-content .resource-info .caret-expander { +.namespace .resource-info .caret-expander { display: inline-block; width: 15px; height: 15px; @@ -142,27 +151,27 @@ body { background-position: 2px center; } -.namespace-content .resource-info.expanded .caret-expander { +.namespace .resource-info.expanded .caret-expander { background-image: url('../images/caret-bottom.svg'); background-position: center 2px; } -.namespace-content .resource-info .extra { +.namespace .resource-info .result-messages { display: none; color: #6a6a6a; } -.namespace-content .resource-info.expanded .extra { +.namespace .resource-info.expanded .result-messages { display: block; } -.namespace-content .extra h4 { +.namespace .result-messages h4 { font-weight: bold; font-size: 15px; margin: 15px 25px 6px; } -.namespace-content .extra ul { +.namespace .result-messages ul { list-style-type: none; font-size: 13px; line-height: 20px; @@ -171,11 +180,11 @@ body { color: #6a6a6a; } -.namespace-content .extra ul li { +.namespace .result-messages ul li { margin-bottom: 5px; } -.namespace-content .extra i { +.namespace .result-messages i { display: inline-block; margin-right: 7px; text-align: center; @@ -186,54 +195,54 @@ body { bottom: -3px; } -.namespace-content .extra .success i { +.namespace .result-messages .success i { color: #8BD2DC; } -.namespace-content .extra .warning i { +.namespace .result-messages .warning i { color: #f26c21; } -.namespace-content .extra .error i { +.namespace .result-messages .error i { color: #a11f4c; } -.namespace-content td.status-bar { +.namespace td.status-bar { vertical-align: top; padding-top: 18px; } -.namespace-content .status { +.namespace .status { float: right; width: 200px; } -.namespace-content .status div { +.namespace .status div { height: 15px; border-radius: 10px; } -.namespace-content .status .passing { +.namespace .status .passing { background-color: #8BD2DC; float: left; } -.namespace-content .status .warning { +.namespace .status .warning { background-color: #f26c21; float: left; } +.namespace .status .failing { + background-color: #a11f4c; + width: 200px; + animation: fadeIn 2s; +} + @keyframes fadeIn { 0% {opacity: 0;} 100% {opacity: 1;} } -.namespace-content .status .failing { - background-color: #a11f4c; - width: 200px; - animation: fadeIn 2s; -} - .footer { text-align: center; padding-top: 10px; diff --git a/public/js/charts.js b/public/js/charts.js index a11e7076..46244a05 100644 --- a/public/js/charts.js +++ b/public/js/charts.js @@ -1,57 +1,4 @@ -if (!Object.values) { - Object.values = function (obj) { - return Object.keys(obj).map(function (key) { - return obj[key]; - }) - } -} - $(function () { - var namespaceChart = new Chart("namespaceScoreChart", { - type: 'bar', - data: { - labels: Object.keys(fairwindsAuditData.NamespacedResults), - datasets: [{ - label: 'Passing', - data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function (r) { return r.Summary.Successes }), - backgroundColor: '#8BD2DC', - }, { - label: 'Warning', - data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function (r) { return r.Summary.Warnings }), - backgroundColor: '#f26c21', - }, { - label: 'Failing', - data: Object.values(fairwindsAuditData.NamespacedResults) - .map(function (r) { return r.Summary.Errors }), - backgroundColor: '#a11f4c', - }] - }, - options: { - legend: { - display: false, - }, - scales: { - xAxes: [{ - stacked: true, - }], - yAxes: [{ - stacked: true, - ticks: { - beginAtZero: true - } - }] - } - } - }); - - var score = fairwindsAuditData.ClusterSummary.Successes / ( - fairwindsAuditData.ClusterSummary.Successes + - fairwindsAuditData.ClusterSummary.Warnings + - fairwindsAuditData.ClusterSummary.Errors); - score = Math.round(score * 100); - var clusterChart = new Chart("clusterScoreChart", { type: 'doughnut', data: { @@ -66,20 +13,10 @@ $(function () { }] }, options: { - // responsive: false, - cutoutPercentage: 75, + responsive: false, legend: { display: false, }, - elements: { - center: { - text: score, - color: '#333', //Default black - fontStyle: 'Muli', //Default Arial - sidePadding: 40, //Default 20 (as a percentage) - } - } } }); -}); - +}); \ No newline at end of file diff --git a/public/js/main.js b/public/js/main.js index 2eaaafb0..8bf969f4 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1,46 +1,14 @@ +if (!Object.values) { + Object.values = function (obj) { + return Object.keys(obj).map(function (key) { + return obj[key]; + }) + } +} + + $(function () { - // Chart.js plugin for rendering text inside of donut chart - // Credit: https://stackoverflow.com/a/43026361/8870697 - Chart.pluginService.register({ - beforeDraw: function (chart) { - if (chart.config.options.elements.center) { - var ctx = chart.chart.ctx; - var centerConfig = chart.config.options.elements.center; - var fontStyle = centerConfig.fontStyle || 'Arial'; - var txt = centerConfig.text; - var color = centerConfig.color || '#000'; - var sidePadding = centerConfig.sidePadding || 20; - var sidePaddingCalculated = (sidePadding/100) * (chart.innerRadius * 2) - // Start with a base font of 30px - ctx.font = "30px " + fontStyle; - - // Get the width of the string and also the width of the element minus 10 to give it 5px side padding - var stringWidth = ctx.measureText(txt).width; - var elementWidth = (chart.innerRadius * 2) - sidePaddingCalculated; - - // Find out how much the font can grow in width. - var widthRatio = elementWidth / stringWidth; - var newFontSize = Math.floor(30 * widthRatio); - var elementHeight = (chart.innerRadius * 2); - - // Pick a new font size so it will not be larger than the height of label. - var fontSizeToUse = Math.min(newFontSize, elementHeight); - - // Set font settings to draw it correctly. - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - var centerX = ((chart.chartArea.left + chart.chartArea.right) / 2); - var centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2); - ctx.font = "bold "+fontSizeToUse+"px " + fontStyle; - ctx.fillStyle = color; - - // Draw text in center - ctx.fillText(txt, centerX, centerY); - } - } - }); - - $('.namespace .resource-info .name').on('click', function(e) { + $('.namespace .resource-info .name').on('click', function (e) { console.log('clicked', arguments) console.log('parent', $(e.srcElement).parent('.resource-info')); $(e.srcElement).parents('.resource-info').toggleClass('expanded'); From 0a4050d6b4874afe4116f1de19de2e522fe53c10 Mon Sep 17 00:00:00 2001 From: Rob Scott Date: Wed, 17 Apr 2019 11:00:11 -0400 Subject: [PATCH 3/3] more dashboard updates, including concepty of weather --- pkg/dashboard/dashboard.go | 72 +++++----------- pkg/dashboard/helpers.go | 104 +++++++++++++++++++++++ pkg/dashboard/templates/dashboard.gohtml | 5 +- public/css/main.css | 26 +++--- public/images/ro-logo.png | Bin 5028 -> 15739 bytes 5 files changed, 140 insertions(+), 67 deletions(-) create mode 100644 pkg/dashboard/helpers.go diff --git a/pkg/dashboard/dashboard.go b/pkg/dashboard/dashboard.go index 17b27090..b722fd20 100644 --- a/pkg/dashboard/dashboard.go +++ b/pkg/dashboard/dashboard.go @@ -1,3 +1,17 @@ +// Copyright 2019 ReactiveOps +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package dashboard import ( @@ -44,57 +58,13 @@ func MainHandler(w http.ResponseWriter, r *http.Request, c conf.Configuration, k JSON: template.JS(jsonData), } tmpl, err := template.New(TemplateName).Funcs(template.FuncMap{ - "getWarningWidth": func(rs validator.ResultSummary, fullWidth int) uint { - return uint(float64(rs.Successes+rs.Warnings) / float64(rs.Successes+rs.Warnings+rs.Errors) * float64(fullWidth)) - }, - "getSuccessWidth": func(rs validator.ResultSummary, fullWidth int) uint { - return uint(float64(rs.Successes) / float64(rs.Successes+rs.Warnings+rs.Errors) * float64(fullWidth)) - }, - "getGrade": func(rs validator.ResultSummary) string { - total := (rs.Successes * 2) + rs.Warnings + (rs.Errors * 2) - score := uint((float64(rs.Successes*2) / float64(total)) * 100) - if score >= 97 { - return "A+" - } else if score >= 93 { - return "A" - } else if score >= 90 { - return "A-" - } else if score >= 87 { - return "B+" - } else if score >= 83 { - return "B" - } else if score >= 80 { - return "B-" - } else if score >= 77 { - return "C+" - } else if score >= 73 { - return "C" - } else if score >= 70 { - return "C-" - } else if score >= 67 { - return "D+" - } else if score >= 63 { - return "D" - } else if score >= 60 { - return "D-" - } else { - return "F" - } - }, - "getScore": func(rs validator.ResultSummary) uint { - total := (rs.Successes * 2) + rs.Warnings + (rs.Errors * 2) - return uint((float64(rs.Successes*2) / float64(total)) * 100) - }, - "getIcon": func(rm validator.ResultMessage) string { - switch rm.Type { - case "success": - return "fas fa-check" - case "warning": - return "fas fa-exclamation" - default: - return "fas fa-times" - } - }, + "getWarningWidth": getWarningWidth, + "getSuccessWidth": getSuccessWidth, + "getWeatherIcon": getWeatherIcon, + "getWeatherText": getWeatherText, + "getGrade": getGrade, + "getScore": getScore, + "getIcon": getIcon, }).ParseFiles(TemplateFile) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/dashboard/helpers.go b/pkg/dashboard/helpers.go new file mode 100644 index 00000000..cc63317d --- /dev/null +++ b/pkg/dashboard/helpers.go @@ -0,0 +1,104 @@ +// Copyright 2019 ReactiveOps +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dashboard + +import ( + "github.com/reactiveops/fairwinds/pkg/validator" +) + +func getWarningWidth(rs validator.ResultSummary, fullWidth int) uint { + return uint(float64(rs.Successes+rs.Warnings) / float64(rs.Successes+rs.Warnings+rs.Errors) * float64(fullWidth)) +} + +func getSuccessWidth(rs validator.ResultSummary, fullWidth int) uint { + return uint(float64(rs.Successes) / float64(rs.Successes+rs.Warnings+rs.Errors) * float64(fullWidth)) +} + +func getGrade(rs validator.ResultSummary) string { + score := getScore(rs) + if score >= 97 { + return "A+" + } else if score >= 93 { + return "A" + } else if score >= 90 { + return "A-" + } else if score >= 87 { + return "B+" + } else if score >= 83 { + return "B" + } else if score >= 80 { + return "B-" + } else if score >= 77 { + return "C+" + } else if score >= 73 { + return "C" + } else if score >= 70 { + return "C-" + } else if score >= 67 { + return "D+" + } else if score >= 63 { + return "D" + } else if score >= 60 { + return "D-" + } else { + return "F" + } +} + +func getScore(rs validator.ResultSummary) uint { + total := (rs.Successes * 2) + rs.Warnings + (rs.Errors * 2) + return uint((float64(rs.Successes*2) / float64(total)) * 100) +} + +func getWeatherIcon(rs validator.ResultSummary) string { + score := getScore(rs) + if score >= 90 { + return "fa-sun" + } else if score >= 80 { + return "fa-cloud-sun" + } else if score >= 70 { + return "fa-cloud" + } else if score >= 60 { + return "fa-cloud-rain" + } else { + return "fa-cloud-showers-heavy" + } +} + +func getWeatherText(rs validator.ResultSummary) string { + score := getScore(rs) + if score >= 90 { + return "Smooth sailing" + } else if score >= 80 { + return "Mostly smooth sailing" + } else if score >= 70 { + return "Smooth sailing within sight" + } else if score >= 60 { + return "A little stormy" + } else { + return "Storms ahead, be careful" + } +} + +func getIcon(rm validator.ResultMessage) string { + switch rm.Type { + case "success": + return "fas fa-check" + case "warning": + return "fas fa-exclamation" + default: + return "fas fa-times" + } +} diff --git a/pkg/dashboard/templates/dashboard.gohtml b/pkg/dashboard/templates/dashboard.gohtml index 54de76bd..2d08ca1b 100644 --- a/pkg/dashboard/templates/dashboard.gohtml +++ b/pkg/dashboard/templates/dashboard.gohtml @@ -40,8 +40,9 @@

    Cluster Overview

    -
    {{ getGrade .AuditData.ClusterSummary }}
    -
    Overall Score: {{ getScore .AuditData.ClusterSummary }}%
    +
    +
    {{ getWeatherText .AuditData.ClusterSummary }}
    +
    Grade: {{ getGrade .AuditData.ClusterSummary }} | Score: {{ getScore .AuditData.ClusterSummary }}%
      diff --git a/public/css/main.css b/public/css/main.css index 1fa6fa2b..3e991ad4 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -5,9 +5,7 @@ body { } .header { - background-color: #fff; - padding: 20px 20px; - box-shadow: 0 1px 3px rgba(0,0,0,0.1); + padding: 20px 20px 0; } .header .header-content { @@ -35,7 +33,7 @@ body { } .header .ro-logo { - height: 44px; + height: 40px; } .dashboard-content { @@ -78,28 +76,28 @@ body { margin-left: 20px; } -.namespace .cluster-overview .cluster-score .grade { - font-size: 120px; +.namespace .cluster-overview .cluster-score .weather { + font-size: 110px; font-weight: bold; + color: #777; } -.namespace .cluster-overview .cluster-score .score { +.namespace .cluster-overview .cluster-score .sailing-message { font-size: 16px; + line-height: 28px; margin-left: 7px; font-weight: 300; color: #555; } -.namespace .cluster-overview .cluster-score .score strong { - font-size: 18px; - font-weight: bold; -} - +.namespace .cluster-overview .cluster-score .scores { + font-size: 16px; + margin-top: 10px;} #clusterScoreChart { width: 550px; position: relative; - top: -220px; + top: -254px; left: 120px; } @@ -108,7 +106,7 @@ body { display: inline-block; position: relative; left: -100px; - top: -15px; + top: -52px; } .namespace .cluster-overview .result-messages ul { diff --git a/public/images/ro-logo.png b/public/images/ro-logo.png index 0650a7298255bc7fc89691333aa7b45655712911..3b3c7f382e418ad25ed671e6330b6b32b9a63023 100644 GIT binary patch literal 15739 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⪚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