new dashboard layout with charts

This commit is contained in:
Rob Scott
2019-01-28 14:28:23 -05:00
parent 700b645126
commit cc37116ddf
6 changed files with 569 additions and 7 deletions

View File

@@ -40,7 +40,7 @@ var FairwindsName = "fairwinds"
var log = logf.Log.WithName(FairwindsName)
func main() {
// dashboard := flag.Bool("dashboard", false, "Runs the webserver for Fairwinds dashboard.")
dashboard := flag.Bool("dashboard", false, "Runs the webserver for Fairwinds dashboard.")
webhook := flag.Bool("webhook", false, "Runs the webhook webserver.")
var disableWebhookConfigInstaller bool
@@ -59,9 +59,9 @@ func main() {
startWebhookServer(c, disableWebhookConfigInstaller)
}
// if *dashboard {
startDashboardServer(c)
// }
if *dashboard {
startDashboardServer(c)
}
}
func startDashboardServer(c conf.Configuration) {

View File

@@ -17,7 +17,7 @@ type DashboardData struct {
NamespacedResults []validator.NamespacedResult
}
var tmpl = template.Must(template.ParseFiles("pkg/dashboard/templates/dashboard.gohtml"))
var tmpl = template.Must(template.ParseFiles("pkg/dashboard/templates/charts.gohtml"))
func Render(w http.ResponseWriter, r *http.Request, c conf.Configuration) {
dashboardData := getDashboardData()

View File

@@ -0,0 +1,177 @@
<!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>
</head>
<body>
<div class="header">
<h1>Fairwinds</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 .NamespacedResults }}
<div class="namespace">
<h3>Namespace: <strong>{{ .Namespace }}</strong></h3>
<table class="namespace-content" cellspacing="0">
{{ 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>
{{ end }}
</table>
</div>
{{ end }}
</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: '#006469',
},{
label: 'Warning',
data: [85, 54, 28, 23, 21],
backgroundColor: '#AE7500',
},{
label: 'Failing',
data: [38, 24, 18, 15, 12],
backgroundColor: '#AE0400',
}]
},
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: ['#006469','#AE7500','#AE0400'],
}]
},
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>

192
public/charts.html Normal file
View File

@@ -0,0 +1,192 @@
<!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>Fairwinds</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: '#006469',
},{
label: 'Warning',
data: [85, 54, 28, 23, 21],
backgroundColor: '#AE7500',
},{
label: 'Failing',
data: [38, 24, 18, 15, 12],
backgroundColor: '#AE0400',
}]
},
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: ['#006469','#AE7500','#AE0400'],
}]
},
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>

193
public/css/charts.css Normal file
View File

@@ -0,0 +1,193 @@
body {
margin: 0;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
background: #eee;
}
.header {
background-color: #fff;
color: #006369;
padding: 20px 30px;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.header h1 {
font-family: 'Lobster', sans-serif;
font-size: 60px;
font-weight: normal;
margin: 0 auto;
width: 900px;
}
.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 .caret-expander {
display: inline-block;
width: 15px;
height: 15px;
margin-right: 10px;
background-image: url('../images/caret-right.svg');
background-size: 15px auto;
background-repeat: no-repeat;
background-position: 2px center;
}
.namespace-content .caret-expander.expanded {
background-image: url('../images/caret-bottom.svg');
background-position: center center;
}
.namespace-content .extra {
list-style-type: none;
font-size: 14px;
line-height: 19px;
margin: 10px 30px;
padding: 0;
}
.namespace-content .extra span {
display: inline-block;
text-align: center;
width: 20px;
font-size: 20px;
}
.namespace-content .extra .success {
color: #006469;
}
.namespace-content .extra .warning {
color: #AE7500;
}
.namespace-content .extra .warning span {
font-size: 15px;
}
.namespace-content .extra .failure {
color: #AE0400;
}
.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: #006469;
float: left;
}
.namespace-content .status .warning {
background-color: #AE7500;
float: left;
}
@keyframes fadeIn {
0% {opacity: 0;}
100% {opacity: 1;}
}
.namespace-content .status .failing {
background-color: #AE0400;
width: 200px;
animation: fadeIn 2s;
}

View File

@@ -9,8 +9,8 @@
<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/main.css">
<link rel="stylesheet" href="css/normalize.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>