lots of cleanup, simplifying results, including container names in output

This commit is contained in:
Rob Scott
2019-02-06 12:08:37 -05:00
parent 2fd6374dd0
commit c94dc4dbf1
12 changed files with 453 additions and 820 deletions

View File

@@ -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

View File

@@ -1,157 +0,0 @@
<!doctype html>
<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 href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet">
<link rel="stylesheet" href="/static/css/normalize.css">
<link rel="stylesheet" href="/static/css/charts.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 type="text/javascript" src="/static/js/main.js"></script>
</head>
<body>
<div class="header">
<h1>
<img class="logo" src="/static/images/logo.png" alt="Fairwinds" />
<span class="alt-logo">Fairwinds</span>
</h1>
</div>
<div class="dashboard-content">
<div class="charts">
<div class="cluster-score chart-section">
<h3>Overall Score:</h3>
<div style="width:510px; height: 400px; left: -100px; position: relative;">
<canvas id="clusterScoreChart"></canvas>
</div>
</div>
<div class="namespace-score chart-section">
<h3>Scores By Namespace:</h3>
<canvas id="namespaceScoreChart"></canvas>
</div>
</div>
{{ range $namespace, $results := .NamespacedResults }}
<div class="namespace">
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
<table class="namespace-content" cellspacing="0">
{{ range $results.Results }}
<tr>
<td class="resource-info">
<div class="name"><span class="caret-expander"></span>{{ .Type }}: <strong>{{ .Name }}</strong></div>
<ul class="extra">
{{ range .Messages}}
<li class="{{ .Type }}"><span>&#{{ .HTMLSpecialCharCode }};</span> {{ .Message }}</li>
{{ end }}
</ul>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: {{ .Summary.WarningWidth 200 }}px;">
<div class="passing" style="width: {{ .Summary.SuccessWidth 200 }}px;"></div>
</div>
</div>
</div>
</td>
</tr>
{{ end }}
</table>
</div>
{{ end }}
</div>
<script>
$(function () {
var namespaceChart = new Chart("namespaceScoreChart", {
type: 'bar',
data: {
labels: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $namespace }}",
{{ end }}
],
datasets: [{
label: 'Passing',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Successes }}",
{{ end }}
],
backgroundColor: '#230f39',
},{
label: 'Warning',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Warnings }}",
{{ end }}
],
backgroundColor: '#f26c21',
},{
label: 'Failing',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Failures }}",
{{ end }}
],
backgroundColor: '#a11f4c',
}]
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
}
});
var clusterChart = new Chart("clusterScoreChart", {
type: 'doughnut',
data: {
labels: ["Passing", "Warning", "Failing"],
datasets: [{
data: [{{ .ClusterSummary.Successes }}, {{ .ClusterSummary.Warnings }}, {{ .ClusterSummary.Failures }}],
backgroundColor: ['#230f39','#f26c21','#a11f4c'],
}]
},
options: {
// responsive: false,
cutoutPercentage: 75,
legend: {
display: false,
},
elements: {
center: {
text: '{{ .ClusterSummary.Score }}%',
color: '#333', //Default black
fontStyle: 'Helvetica', //Default Arial
sidePadding: 30 //Default 20 (as a percentage)
}
}
}
});
});
</script>
</body>
</html>

View File

@@ -11,68 +11,152 @@
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet">
<link rel="stylesheet" href="/static/css/normalize.css">
<link rel="stylesheet" href="/static/css/main.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 type="text/javascript" src="/static/js/main.js"></script>
</head>
<body>
<div class="header">
<h1>Fairwinds</h1>
<h1>
<img class="logo" src="/static/images/logo.png" alt="Fairwinds" />
<span class="alt-logo">Fairwinds</span>
</h1>
</div>
<div class="cluster-score">
<p>Overall Score:</p>
<h2>{{ .ClusterSummary.Score }}%</h2>
<div class="status">
<div class="failing">
<div class="warning" style="width: {{ .ClusterSummary.WarningWidth 400 }}px;">
<div class="passing" style="width: {{ .ClusterSummary.SuccessWidth 400 }}px;"></div>
<div class="dashboard-content">
<div class="charts">
<div class="cluster-score chart-section">
<h3>Overall Score:</h3>
<div style="width:510px; height: 400px; left: -100px; position: relative;">
<canvas id="clusterScoreChart"></canvas>
</div>
</div>
</div>
<div class="summary">
<div class="item">
<span class="label">Successes:</span>
<strong class="value">{{ .ClusterSummary.Successes }}</strong>
</div>
<div class="item">
<span class="label">Warnings:</span>
<strong class="value">{{ .ClusterSummary.Warnings }}</strong>
</div>
<div class="item">
<span class="label">Failures:</span>
<strong class="value">{{ .ClusterSummary.Failures }}</strong>
<div class="namespace-score chart-section">
<h3>Scores By Namespace:</h3>
<canvas id="namespaceScoreChart"></canvas>
</div>
</div>
</div>
{{ range .NamespacedResults }}
<div class="namespace-header">
<h3>Namespace: <strong>{{ .Namespace }}</strong></h3>
</div>
{{ range $namespace, $results := .NamespacedResults }}
<div class="namespace">
<h3>Namespace: <strong>{{ $namespace }}</strong></h3>
<table class="namespace-content">
{{ range .Results }}
<tr>
<td>
<div class="name"><span class="caret-expander expanded"></span>{{ .Type }}: <strong>{{ .Name }}</strong></div>
<ul class="extra">
{{ range .Messages}}
<li class="{{ .Type }}"><span>&#{{ .HTMLSpecialCharCode }};</span> {{ .Message }}</li>
{{ end }}
</ul>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: {{ .Summary.WarningWidth 200 }}px;">
<div class="passing" style="width: {{ .Summary.SuccessWidth 200 }}px;"></div>
</div>
</div>
</div>
</td>
</tr>
<table class="namespace-content" cellspacing="0">
{{ range $results.Results }}
<tr>
<td class="resource-info">
<div class="name"><span class="caret-expander"></span>{{ .Type }}: <strong>{{ .Name }}</strong></div>
{{ range .ContainerResults}}
<div class="extra">
<h4>{{ .Name }}</h4>
<ul>
{{ range .Messages}}
<li class="{{ .Type }}"><span>&#{{ .HTMLSpecialCharCode }};</span> {{ .Message }}</li>
{{ end }}
</ul>
</div>
{{ end }}
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: {{ .Summary.WarningWidth 200 }}px;">
<div class="passing" style="width: {{ .Summary.SuccessWidth 200 }}px;"></div>
</div>
</div>
</div>
</td>
</tr>
{{ end }}
</table>
</div>
{{ end }}
</table>
{{ end }}
</div>
<script>
$(function () {
var namespaceChart = new Chart("namespaceScoreChart", {
type: 'bar',
data: {
labels: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $namespace }}",
{{ end }}
],
datasets: [{
label: 'Passing',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Successes }}",
{{ end }}
],
backgroundColor: '#8BD2DC',
},{
label: 'Warning',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Warnings }}",
{{ end }}
],
backgroundColor: '#f26c21',
},{
label: 'Failing',
data: [
{{ range $namespace, $results := .NamespacedResults }}
"{{ $results.Summary.Failures }}",
{{ end }}
],
backgroundColor: '#a11f4c',
}]
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
}
});
var clusterChart = new Chart("clusterScoreChart", {
type: 'doughnut',
data: {
labels: ["Passing", "Warning", "Failing"],
datasets: [{
data: [{{ .ClusterSummary.Successes }}, {{ .ClusterSummary.Warnings }}, {{ .ClusterSummary.Failures }}],
backgroundColor: ['#8BD2DC','#f26c21','#a11f4c'],
}]
},
options: {
// responsive: false,
cutoutPercentage: 75,
legend: {
display: false,
},
elements: {
center: {
text: '{{ .ClusterSummary.Score }}%',
color: '#333', //Default black
fontStyle: 'Helvetica', //Default Arial
sidePadding: 30 //Default 20 (as a percentage)
}
}
}
});
});
</script>
</body>
</html>

View File

@@ -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,
)
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -1,195 +0,0 @@
<!doctype html>
<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 href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/charts.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
</head>
<body>
<div class="header">
<h1>
<img class="logo" src="images/logo.png" alt="Fairwinds" />
<span class="alt-logo">Fairwinds</span>
</h1>
</div>
<div class="dashboard-content">
<div class="charts">
<div class="cluster-score chart-section">
<h3>Overall Score:</h3>
<div style="width:510px; height: 400px; left: -100px; position: relative;">
<canvas id="clusterScoreChart"></canvas>
</div>
</div>
<div class="namespace-score chart-section">
<h3>Scores By Namespace:</h3>
<canvas id="namespaceScoreChart"></canvas>
</div>
</div>
<div class="namespace">
<h3>Namespace: <strong>kube-system</strong></h3>
<table class="namespace-content" cellspacing="0">
<tr>
<td>
<div class="name"><span class="caret-expander"></span>DaemonSet: <strong>datadog-agent</strong></div>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 124px;">
<div class="passing" style="width: 82px;"></div>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="name"><span class="caret-expander expanded"></span>Deployment: <strong>tiller-deployment</strong></div>
<ul class="extra">
<li class="success"><span>&#9745;</span> Image Tag Specified</li>
<li class="success"><span>&#9745;</span> Resource Limits</li>
<li class="success"><span>&#9745;</span> Resource Requests</li>
<li class="warning"><span>&#9888;</span> Run As Non Root User</li>
<li class="failure"><span>&#9746;</span> Liveness Probe</li>
<li class="failure"><span>&#9746;</span> Readiness Probe</li>
</ul>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 150px;">
<div class="passing" style="width: 106px;"></div>
</div>
</div>
</div>
</td>
</tr>
</table>
</div>
</div>
<script>
window.onload = function renderCharts() {
Chart.pluginService.register({
beforeDraw: function (chart) {
if (chart.config.options.elements.center) {
//Get ctx from string
var ctx = chart.chart.ctx;
//Get options from the center object in options
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);
}
}
});
var namespaceChart = new Chart("namespaceScoreChart", {
type: 'bar',
data: {
labels: ["kube-system", "development", "staging", "infra", "default"],
datasets: [{
label: 'Passing',
data: [118, 78, 65, 56, 43],
backgroundColor: '#230f39',
}, {
label: 'Warning',
data: [85, 54, 28, 23, 21],
backgroundColor: '#f26c21',
}, {
label: 'Failing',
data: [38, 24, 18, 15, 12],
backgroundColor: '#a11f4c',
}]
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
}
});
}
var clusterChart = new Chart("clusterScoreChart", {
type: 'doughnut',
data: {
labels: ["Passing", "Warning", "Failing"],
datasets: [{
data: [118, 34, 28],
backgroundColor: ['#230f39', '#f26c21', '#a11f4c'],
}]
},
options: {
// responsive: false,
cutoutPercentage: 75,
legend: {
display: false,
},
elements: {
center: {
text: '64%',
color: '#333', //Default black
fontStyle: 'Helvetica', //Default Arial
sidePadding: 30 //Default 20 (as a percentage)
}
}
}
});
</script>
</body>
</html>

View File

@@ -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;
}

View File

@@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 344 KiB

View File

@@ -11,82 +11,185 @@
<link href="https://fonts.googleapis.com/css?family=Lobster" rel="stylesheet">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.2/Chart.min.js"></script>
</head>
<body>
<div class="header">
<h1>Fairwinds</h1>
<h1>
<img class="logo" src="images/logo.png" alt="Fairwinds" />
<span class="alt-logo">Fairwinds</span>
</h1>
</div>
<div class="cluster-score">
<p>Overall Score:</p>
<h2>64%</h2>
<div class="status">
<div class="failing">
<div class="warning" style="width: 300px;">
<div class="passing" style="width: 216px;"></div>
<div class="dashboard-content">
<div class="charts">
<div class="cluster-score chart-section">
<h3>Overall Score:</h3>
<div style="width:510px; height: 400px; left: -100px; position: relative;">
<canvas id="clusterScoreChart"></canvas>
</div>
</div>
<div class="namespace-score chart-section">
<h3>Scores By Namespace:</h3>
<canvas id="namespaceScoreChart"></canvas>
</div>
</div>
<div class="summary">
<div class="item">
<span class="label">Successes:</span>
<strong class="value">118</strong>
</div>
<div class="item">
<span class="label">Warnings:</span>
<strong class="value">34</strong>
</div>
<div class="item">
<span class="label">Failures:</span>
<strong class="value">28</strong>
</div>
<div class="namespace">
<h3>Namespace: <strong>kube-system</strong></h3>
<table class="namespace-content" cellspacing="0">
<tr>
<td>
<div class="name"><span class="caret-expander"></span>DaemonSet: <strong>datadog-agent</strong></div>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 124px;">
<div class="passing" style="width: 82px;"></div>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="name"><span class="caret-expander expanded"></span>Deployment: <strong>tiller-deployment</strong></div>
<ul class="extra">
<li class="success"><span>&#9745;</span> Image Tag Specified</li>
<li class="success"><span>&#9745;</span> Resource Limits</li>
<li class="success"><span>&#9745;</span> Resource Requests</li>
<li class="warning"><span>&#9888;</span> Run As Non Root User</li>
<li class="failure"><span>&#9746;</span> Liveness Probe</li>
<li class="failure"><span>&#9746;</span> Readiness Probe</li>
</ul>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 150px;">
<div class="passing" style="width: 106px;"></div>
</div>
</div>
</div>
</td>
</tr>
</table>
</div>
</div>
<div class="namespace-header">
<h3>Namespace: <strong>kube-system</strong></h3>
</div>
<script>
window.onload = function renderCharts() {
Chart.pluginService.register({
beforeDraw: function (chart) {
if (chart.config.options.elements.center) {
//Get ctx from string
var ctx = chart.chart.ctx;
<table class="namespace-content" cellspacing="0">
<tr>
<td>
<div class="name"><span class="caret-expander"></span>DaemonSet: <strong>datadog-agent</strong></div>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 124px;">
<div class="passing" style="width: 82px;"></div>
</div>
</div>
</div>
</td>
</tr>
<tr>
<td>
<div class="name"><span class="caret-expander expanded"></span>Deployment: <strong>tiller-deployment</strong></div>
<ul class="extra">
<li class="pass"><span>&#9745;</span> Image Tag Specified</li>
<li class="pass"><span>&#9745;</span> Resource Limits</li>
<li class="pass"><span>&#9745;</span> Resource Requests</li>
<li class="warn"><span>&#9888;</span> Run As Non Root User</li>
<li class="fail"><span>&#9746;</span> Liveness Probe</li>
<li class="fail"><span>&#9746;</span> Readiness Probe</li>
</ul>
</td>
<td class="status-bar">
<div class="status">
<div class="failing">
<div class="warning" style="width: 150px;">
<div class="passing" style="width: 106px;"></div>
</div>
</div>
</div>
</td>
</tr>
</table>
//Get options from the center object in options
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);
}
}
});
var namespaceChart = new Chart("namespaceScoreChart", {
type: 'bar',
data: {
labels: ["kube-system", "development", "staging", "infra", "default"],
datasets: [{
label: 'Passing',
data: [118, 78, 65, 56, 43],
backgroundColor: '#230f39',
}, {
label: 'Warning',
data: [85, 54, 28, 23, 21],
backgroundColor: '#f26c21',
}, {
label: 'Failing',
data: [38, 24, 18, 15, 12],
backgroundColor: '#a11f4c',
}]
},
options: {
legend: {
display: false,
},
scales: {
xAxes: [{
stacked: true,
}],
yAxes: [{
stacked: true,
ticks: {
beginAtZero: true
}
}]
}
}
});
}
var clusterChart = new Chart("clusterScoreChart", {
type: 'doughnut',
data: {
labels: ["Passing", "Warning", "Failing"],
datasets: [{
data: [118, 34, 28],
backgroundColor: ['#230f39', '#f26c21', '#a11f4c'],
}]
},
options: {
// responsive: false,
cutoutPercentage: 75,
legend: {
display: false,
},
elements: {
center: {
text: '64%',
color: '#333', //Default black
fontStyle: 'Helvetica', //Default Arial
sidePadding: 30 //Default 20 (as a percentage)
}
}
}
});
</script>
</body>
</html>
</html>