refactor: html output custom style

main
sundowndev 2021-06-08 17:14:28 +02:00
parent a14a72f212
commit a27d993b13
7 changed files with 1129 additions and 71 deletions

View File

@ -4,16 +4,28 @@
<title>Driftctl scan report</title> <title>Driftctl scan report</title>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="shortcut icon"
href="https://raw.githubusercontent.com/cloudskiff/driftctl-docs/main/static/img/favicon.ico"/>
<style>{{.Stylesheet}}</style> <style>{{.Stylesheet}}</style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<div class="heading d-flex justify-space-between align-center"> <div class="heading d-flex justify-space-between align-center">
<div> <div>
<h1 class="heading-title">Driftctl scan report</h1> <h1 class="heading-title mb-1">Driftctl scan report</h1>
<span class="heading-subtitle">Coverage {{.Coverage}}%</span> <span class="heading-subtitle">Coverage {{.Coverage}}%</span>
</div> </div>
<h2 class="heading-date">{{ .ScanDate }}</h2> <div class="text--right">
<h2 class="heading-date mb-1">{{ .ScanDate }}</h2>
<span class="heading-subtitle">Scan duration {{.ScanDuration}}</span>
</div>
</div>
<div class="card d-flex justify-space-around mb-2 text--grey">
<span>Total resources: {{.Summary.TotalResources}}</span>
<span>Managed: {{rate .Summary.TotalManaged}}%</span>
<span>Changed: {{.Summary.TotalDrifted}}/{{.Summary.TotalManaged}}</span>
<span>Unmanaged: {{rate .Summary.TotalUnmanaged}}%</span>
<span>Missing: {{rate .Summary.TotalDeleted}}%</span>
</div> </div>
<div class="app-content"> <div class="app-content">
{{ if (lt .Coverage 100) }} {{ if (lt .Coverage 100) }}
@ -56,16 +68,24 @@
<div class="panels"> <div class="panels">
{{ if (gt (len .Unmanaged) 0) }} {{ if (gt (len .Unmanaged) 0) }}
<div class="panel" id="one-panel"> <div class="panel" id="one-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
{{range $res := .Unmanaged}} {{range $res := .Unmanaged}}
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">{{$res.TerraformId}}</span> <span class="resource-item-id">{{$res.TerraformId}}</span>
<span class="resource-item-type text--bold">{{$res.TerraformType}}</span> <span class="resource-item-type">{{$res.TerraformType}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{ if (gt (len .Differences) 0) }} {{ if (gt (len .Differences) 0) }}
<div class="panel" id="two-panel"> <div class="panel" id="two-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
{{range $diff := .Differences}} {{range $diff := .Differences}}
<div class="resource-item resource-item-changed d-flex justify-space-between"> <div class="resource-item resource-item-changed d-flex justify-space-between">
<span class="resource-item-id">{{$diff.Res.TerraformId}}</span> <span class="resource-item-id">{{$diff.Res.TerraformId}}</span>
@ -74,17 +94,21 @@
<div>{{ formatChange $change }}</div> <div>{{ formatChange $change }}</div>
{{end}} {{end}}
</div> </div>
<span class="resource-item-type text--bold">{{$diff.Res.TerraformType}}</span> <span class="resource-item-type">{{$diff.Res.TerraformType}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>
{{end}} {{end}}
{{ if (gt (len .Deleted) 0) }} {{ if (gt (len .Deleted) 0) }}
<div class="panel" id="three-panel"> <div class="panel" id="three-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
{{range $res := .Deleted}} {{range $res := .Deleted}}
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">{{$res.TerraformId}}</span> <span class="resource-item-id">{{$res.TerraformId}}</span>
<span class="resource-item-type text--bold">{{$res.TerraformType}}</span> <span class="resource-item-type">{{$res.TerraformType}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>
@ -94,8 +118,8 @@
{{range $type, $messages := .Alerts}} {{range $type, $messages := .Alerts}}
{{range $el := $messages}} {{range $el := $messages}}
<div class="resource-item resource-item-alerts"> <div class="resource-item resource-item-alerts">
<span class="resource-item-type text--bold">{{ $type }}</span> <span class="resource-item-type">{{ $type }}</span>
<span>- {{ $el.Message }}</span> <span>{{ $el.Message }}</span>
</div> </div>
{{end}} {{end}}
{{end}} {{end}}

View File

@ -1,5 +1,3 @@
@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600&display=swap');
/* http://meyerweb.com/eric/tools/css/reset/ /* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126 v2.0 | 20110126
License: none (public domain) License: none (public domain)
@ -20,7 +18,6 @@ time, mark, audio, video {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
font-size: 100%;
font: inherit; font: inherit;
vertical-align: baseline; vertical-align: baseline;
} }
@ -45,7 +42,6 @@ blockquote, q {
blockquote:before, blockquote:after, blockquote:before, blockquote:after,
q:before, q:after { q:before, q:after {
content: '';
content: none; content: none;
} }
@ -62,7 +58,7 @@ table {
} }
body { body {
font-family: 'Source Sans Pro', sans-serif; font-family: "Helvetica", sans-serif;
color: #1C1E21; color: #1C1E21;
background-color: #F7F7F9; background-color: #F7F7F9;
padding-bottom: 50px; padding-bottom: 50px;
@ -127,6 +123,26 @@ div.container {
margin-bottom: 10px; margin-bottom: 10px;
} }
.pa-1 {
padding: 10px;
}
.pl-1 {
padding-left: 10px;
}
.pr-1 {
padding-right: 10px;
}
.pt-1 {
padding-top: 10px;
}
.pb-1 {
padding-bottom: 10px;
}
input[type="text"], input[type="text"],
select { select {
padding: 8px; padding: 8px;
@ -144,6 +160,7 @@ select:focus {
input[name="resource-id-filter"], select[name="resource-type-filter"] { input[name="resource-id-filter"], select[name="resource-type-filter"] {
width: 300px; width: 300px;
border-radius: 3px;
} }
h1 { h1 {
@ -157,21 +174,32 @@ h2 {
} }
.heading-title { .heading-title {
margin-bottom: 10px; display: block;
}
.heading-subtitle {
font-size: 14px;
display: block;
} }
.app-content { .app-content {
padding: 25px; padding: 25px;
border-top: 3px solid #71B2C3; border-top: 3px solid #71B2C3;
background-color: #ffffff; background-color: #ffffff;
border-radius: 0 0 10px 10px; box-shadow: 0 0 5px #0000000a;
} }
.resource-item { .resource-item {
border: 1px solid #ececec; border-top: 1px solid #ececec;
padding: 10px; border-left: 1px solid #ececec;
margin-top: 10px; border-right: 1px solid #ececec;
padding: 15px;
color: #6e7071; color: #6e7071;
font-size: 14px;
}
.resource-item:last-child {
border-bottom: 1px solid #ececec;
} }
.resource-item:hover { .resource-item:hover {
@ -182,6 +210,22 @@ h2 {
font-weight: bold; font-weight: bold;
} }
.text--grey {
color: #747578;
}
.text--right {
text-align: right;
}
.card {
padding: 15px;
font-size: 15px;
background: #ffffff;
box-shadow: 0 0 5px #0000000a;
border-radius: 3px;
}
.reset-filter-btn { .reset-filter-btn {
border: none; border: none;
padding: 8px; padding: 8px;
@ -206,6 +250,7 @@ h2 {
display: inline-block; display: inline-block;
color: #747578; color: #747578;
transition: background-color 200ms; transition: background-color 200ms;
border-radius: 3px;
} }
.tab:hover { .tab:hover {
@ -251,9 +296,8 @@ h2 {
#two:checked ~ .tabs #two-tab, #two:checked ~ .tabs #two-tab,
#three:checked ~ .tabs #three-tab, #three:checked ~ .tabs #three-tab,
#four:checked ~ .tabs #four-tab { #four:checked ~ .tabs #four-tab {
background: transparent; background: #71b2c3;
color: #747578; color: #ffffff;
border-bottom: 2px solid #71b2c3;
} }
.empty-message-container { .empty-message-container {

View File

@ -4,6 +4,7 @@ import (
"embed" "embed"
"fmt" "fmt"
"html/template" "html/template"
"math"
"os" "os"
"strings" "strings"
"time" "time"
@ -26,14 +27,15 @@ type HTML struct {
} }
type HTMLTemplateParams struct { type HTMLTemplateParams struct {
ScanDate string ScanDate string
Coverage int Coverage int
Summary analyser.Summary Summary analyser.Summary
Unmanaged []resource.Resource Unmanaged []resource.Resource
Differences []analyser.Difference Differences []analyser.Difference
Deleted []resource.Resource Deleted []resource.Resource
Alerts alerter.Alerts Alerts alerter.Alerts
Stylesheet template.CSS Stylesheet template.CSS
ScanDuration string
} }
func NewHTML(path string) *HTML { func NewHTML(path string) *HTML {
@ -92,6 +94,12 @@ func (c *HTML) Write(analysis *analyser.Analysis) error {
return fmt.Sprintf("%s %s: %s => %s %s", prefix, strings.Join(ch.Path, "."), prettify(ch.From), prettify(ch.To), suffix) return fmt.Sprintf("%s %s: %s => %s %s", prefix, strings.Join(ch.Path, "."), prettify(ch.From), prettify(ch.To), suffix)
}, },
"rate": func(count int) float64 {
if analysis.Summary().TotalResources == 0 {
return 0
}
return math.Round(100 * float64(count) / float64(analysis.Summary().TotalResources))
},
} }
tmpl, err := template.New("main").Funcs(funcMap).Parse(string(tmplFile)) tmpl, err := template.New("main").Funcs(funcMap).Parse(string(tmplFile))
@ -100,14 +108,15 @@ func (c *HTML) Write(analysis *analyser.Analysis) error {
} }
data := &HTMLTemplateParams{ data := &HTMLTemplateParams{
ScanDate: time.Now().Format("Jan 02, 2006"), ScanDate: time.Now().Format("Jan 02, 2006"),
Summary: analysis.Summary(), Summary: analysis.Summary(),
Coverage: analysis.Coverage(), Coverage: analysis.Coverage(),
Unmanaged: analysis.Unmanaged(), Unmanaged: analysis.Unmanaged(),
Differences: analysis.Differences(), Differences: analysis.Differences(),
Deleted: analysis.Deleted(), Deleted: analysis.Deleted(),
Alerts: analysis.Alerts(), Alerts: analysis.Alerts(),
Stylesheet: template.CSS(styleFile), Stylesheet: template.CSS(styleFile),
ScanDuration: analysis.Duration.Round(time.Second).String(),
} }
err = tmpl.Execute(file, data) err = tmpl.Execute(file, data)

View File

@ -4,6 +4,7 @@ import (
"io/ioutil" "io/ioutil"
"path" "path"
"testing" "testing"
"time"
"github.com/cloudskiff/driftctl/pkg/resource" "github.com/cloudskiff/driftctl/pkg/resource"
testresource "github.com/cloudskiff/driftctl/test/resource" testresource "github.com/cloudskiff/driftctl/test/resource"
@ -21,12 +22,38 @@ func TestHTML_Write(t *testing.T) {
analysis func() *analyser.Analysis analysis func() *analyser.Analysis
err error err error
}{ }{
{
name: "test html output when there's no resources",
goldenfile: "output_empty.html",
analysis: func() *analyser.Analysis {
a := &analyser.Analysis{}
return a
},
err: nil,
},
{
name: "test html output when infrastructure is in sync",
goldenfile: "output_sync.html",
analysis: func() *analyser.Analysis {
a := &analyser.Analysis{}
a.Duration = 72 * time.Second
a.AddManaged(
&testresource.FakeResource{
Id: "deleted-id-3",
Type: "aws_deleted_resource",
},
)
return a
},
err: nil,
},
{ {
name: "test html output", name: "test html output",
goldenfile: "output.html", goldenfile: "output.html",
analysis: func() *analyser.Analysis { analysis: func() *analyser.Analysis {
a := fakeAnalysisWithAlerts() a := fakeAnalysisWithAlerts()
a.Duration = 91 * time.Second
a.AddDeleted( a.AddDeleted(
&testresource.FakeResource{ &testresource.FakeResource{
Id: "deleted-id-3", Id: "deleted-id-3",

View File

@ -4,9 +4,9 @@
<title>Driftctl scan report</title> <title>Driftctl scan report</title>
<meta charset="UTF-8"/> <meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<style>@import url('https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@300;400;600&display=swap'); <link rel="shortcut icon"
href="https://raw.githubusercontent.com/cloudskiff/driftctl-docs/main/static/img/favicon.ico"/>
/* http://meyerweb.com/eric/tools/css/reset/ <style>/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126 v2.0 | 20110126
License: none (public domain) License: none (public domain)
*/ */
@ -26,7 +26,6 @@ time, mark, audio, video {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
font-size: 100%;
font: inherit; font: inherit;
vertical-align: baseline; vertical-align: baseline;
} }
@ -51,7 +50,6 @@ blockquote, q {
blockquote:before, blockquote:after, blockquote:before, blockquote:after,
q:before, q:after { q:before, q:after {
content: '';
content: none; content: none;
} }
@ -68,7 +66,7 @@ table {
} }
body { body {
font-family: 'Source Sans Pro', sans-serif; font-family: "Helvetica", sans-serif;
color: #1C1E21; color: #1C1E21;
background-color: #F7F7F9; background-color: #F7F7F9;
padding-bottom: 50px; padding-bottom: 50px;
@ -133,6 +131,26 @@ div.container {
margin-bottom: 10px; margin-bottom: 10px;
} }
.pa-1 {
padding: 10px;
}
.pl-1 {
padding-left: 10px;
}
.pr-1 {
padding-right: 10px;
}
.pt-1 {
padding-top: 10px;
}
.pb-1 {
padding-bottom: 10px;
}
input[type="text"], input[type="text"],
select { select {
padding: 8px; padding: 8px;
@ -150,6 +168,7 @@ select:focus {
input[name="resource-id-filter"], select[name="resource-type-filter"] { input[name="resource-id-filter"], select[name="resource-type-filter"] {
width: 300px; width: 300px;
border-radius: 3px;
} }
h1 { h1 {
@ -163,21 +182,32 @@ h2 {
} }
.heading-title { .heading-title {
margin-bottom: 10px; display: block;
}
.heading-subtitle {
font-size: 14px;
display: block;
} }
.app-content { .app-content {
padding: 25px; padding: 25px;
border-top: 3px solid #71B2C3; border-top: 3px solid #71B2C3;
background-color: #ffffff; background-color: #ffffff;
border-radius: 0 0 10px 10px; box-shadow: 0 0 5px #0000000a;
} }
.resource-item { .resource-item {
border: 1px solid #ececec; border-top: 1px solid #ececec;
padding: 10px; border-left: 1px solid #ececec;
margin-top: 10px; border-right: 1px solid #ececec;
padding: 15px;
color: #6e7071; color: #6e7071;
font-size: 14px;
}
.resource-item:last-child {
border-bottom: 1px solid #ececec;
} }
.resource-item:hover { .resource-item:hover {
@ -188,6 +218,22 @@ h2 {
font-weight: bold; font-weight: bold;
} }
.text--grey {
color: #747578;
}
.text--right {
text-align: right;
}
.card {
padding: 15px;
font-size: 15px;
background: #ffffff;
box-shadow: 0 0 5px #0000000a;
border-radius: 3px;
}
.reset-filter-btn { .reset-filter-btn {
border: none; border: none;
padding: 8px; padding: 8px;
@ -212,6 +258,7 @@ h2 {
display: inline-block; display: inline-block;
color: #747578; color: #747578;
transition: background-color 200ms; transition: background-color 200ms;
border-radius: 3px;
} }
.tab:hover { .tab:hover {
@ -257,9 +304,8 @@ h2 {
#two:checked ~ .tabs #two-tab, #two:checked ~ .tabs #two-tab,
#three:checked ~ .tabs #three-tab, #three:checked ~ .tabs #three-tab,
#four:checked ~ .tabs #four-tab { #four:checked ~ .tabs #four-tab {
background: transparent; background: #71b2c3;
color: #747578; color: #ffffff;
border-bottom: 2px solid #71b2c3;
} }
.empty-message-container { .empty-message-container {
@ -270,10 +316,20 @@ h2 {
<div class="container"> <div class="container">
<div class="heading d-flex justify-space-between align-center"> <div class="heading d-flex justify-space-between align-center">
<div> <div>
<h1 class="heading-title">Driftctl scan report</h1> <h1 class="heading-title mb-1">Driftctl scan report</h1>
<span class="heading-subtitle">Coverage 15%</span> <span class="heading-subtitle">Coverage 15%</span>
</div> </div>
<h2 class="heading-date">Jun 07, 2021</h2> <div class="text--right">
<h2 class="heading-date mb-1">Jun 08, 2021</h2>
<span class="heading-subtitle">Scan duration 1m31s</span>
</div>
</div>
<div class="card d-flex justify-space-around mb-2 text--grey">
<span>Total resources: 13</span>
<span>Managed: 15%</span>
<span>Changed: 2/2</span>
<span>Unmanaged: 38%</span>
<span>Missing: 46%</span>
</div> </div>
<div class="app-content"> <div class="app-content">
@ -320,36 +376,44 @@ h2 {
<div class="panels"> <div class="panels">
<div class="panel" id="one-panel"> <div class="panel" id="one-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">unmanaged-id-1</span> <span class="resource-item-id">unmanaged-id-1</span>
<span class="resource-item-type text--bold">aws_unmanaged_resource</span> <span class="resource-item-type">aws_unmanaged_resource</span>
</div> </div>
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">unmanaged-id-2</span> <span class="resource-item-id">unmanaged-id-2</span>
<span class="resource-item-type text--bold">aws_unmanaged_resource</span> <span class="resource-item-type">aws_unmanaged_resource</span>
</div> </div>
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">unmanaged-id-3</span> <span class="resource-item-id">unmanaged-id-3</span>
<span class="resource-item-type text--bold">aws_unmanaged_resource</span> <span class="resource-item-type">aws_unmanaged_resource</span>
</div> </div>
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">unmanaged-id-4</span> <span class="resource-item-id">unmanaged-id-4</span>
<span class="resource-item-type text--bold">aws_unmanaged_resource</span> <span class="resource-item-type">aws_unmanaged_resource</span>
</div> </div>
<div class="resource-item resource-item-unmanaged d-flex justify-space-between"> <div class="resource-item resource-item-unmanaged d-flex justify-space-between">
<span class="resource-item-id">unmanaged-id-5</span> <span class="resource-item-id">unmanaged-id-5</span>
<span class="resource-item-type text--bold">aws_unmanaged_resource</span> <span class="resource-item-type">aws_unmanaged_resource</span>
</div> </div>
</div> </div>
<div class="panel" id="two-panel"> <div class="panel" id="two-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
<div class="resource-item resource-item-changed d-flex justify-space-between"> <div class="resource-item resource-item-changed d-flex justify-space-between">
<span class="resource-item-id">diff-id-1</span> <span class="resource-item-id">diff-id-1</span>
@ -362,7 +426,7 @@ h2 {
<div>- a: &#34;oldValue&#34; =&gt; &lt;nil&gt; </div> <div>- a: &#34;oldValue&#34; =&gt; &lt;nil&gt; </div>
</div> </div>
<span class="resource-item-type text--bold">aws_diff_resource</span> <span class="resource-item-type">aws_diff_resource</span>
</div> </div>
<div class="resource-item resource-item-changed d-flex justify-space-between"> <div class="resource-item resource-item-changed d-flex justify-space-between">
@ -372,42 +436,46 @@ h2 {
<div>- path.to.field: &lt;nil&gt; =&gt; [&#34;value&#34;] </div> <div>- path.to.field: &lt;nil&gt; =&gt; [&#34;value&#34;] </div>
</div> </div>
<span class="resource-item-type text--bold">aws_diff_resource</span> <span class="resource-item-type">aws_diff_resource</span>
</div> </div>
</div> </div>
<div class="panel" id="three-panel"> <div class="panel" id="three-panel">
<div class="d-flex justify-space-between text--grey pa-1">
<span>Resource id</span>
<span>Resource type</span>
</div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-1</span> <span class="resource-item-id">deleted-id-1</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-2</span> <span class="resource-item-id">deleted-id-2</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-3</span> <span class="resource-item-id">deleted-id-3</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-4</span> <span class="resource-item-id">deleted-id-4</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-5</span> <span class="resource-item-id">deleted-id-5</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
<div class="resource-item resource-item-deleted d-flex justify-space-between"> <div class="resource-item resource-item-deleted d-flex justify-space-between">
<span class="resource-item-id">deleted-id-6</span> <span class="resource-item-id">deleted-id-6</span>
<span class="resource-item-type text--bold">aws_deleted_resource</span> <span class="resource-item-type">aws_deleted_resource</span>
</div> </div>
</div> </div>
@ -417,18 +485,18 @@ h2 {
<div class="resource-item resource-item-alerts"> <div class="resource-item resource-item-alerts">
<span class="resource-item-type text--bold"></span> <span class="resource-item-type"></span>
<span>- Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden.</span> <span>Ignoring aws_vpc from drift calculation: Listing aws_vpc is forbidden.</span>
</div> </div>
<div class="resource-item resource-item-alerts"> <div class="resource-item resource-item-alerts">
<span class="resource-item-type text--bold"></span> <span class="resource-item-type"></span>
<span>- Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden.</span> <span>Ignoring aws_sqs from drift calculation: Listing aws_sqs is forbidden.</span>
</div> </div>
<div class="resource-item resource-item-alerts"> <div class="resource-item resource-item-alerts">
<span class="resource-item-type text--bold"></span> <span class="resource-item-type"></span>
<span>- Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden.</span> <span>Ignoring aws_sns from drift calculation: Listing aws_sns is forbidden.</span>
</div> </div>

View File

@ -0,0 +1,458 @@
<!doctype html>
<html>
<head>
<title>Driftctl scan report</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="shortcut icon"
href="https://raw.githubusercontent.com/cloudskiff/driftctl-docs/main/static/img/favicon.ico"/>
<style>/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/*
* Custom style
*/
:root {
--transitionDuration: 400ms;
}
body {
font-family: "Helvetica", sans-serif;
color: #1C1E21;
background-color: #F7F7F9;
padding-bottom: 50px;
}
div.container {
max-width: 100%;
width: 1280px;
margin: auto;
}
.heading {
height: 130px;
}
.hide {
display: none !important;
}
.d-flex {
display: flex;
flex-direction: row;
}
.justify-center {
justify-content: center;
}
.justify-space-around {
justify-content: space-around;
}
.justify-space-between {
justify-content: space-between;
}
.align-center {
align-items: center;
}
.mt-5 {
margin-top: 50px;
}
.mb-5 {
margin-bottom: 50px;
}
.mt-2 {
margin-top: 20px;
}
.mb-2 {
margin-bottom: 20px;
}
.mt-1 {
margin-top: 10px;
}
.mb-1 {
margin-bottom: 10px;
}
.pa-1 {
padding: 10px;
}
.pl-1 {
padding-left: 10px;
}
.pr-1 {
padding-right: 10px;
}
.pt-1 {
padding-top: 10px;
}
.pb-1 {
padding-bottom: 10px;
}
input[type="text"],
select {
padding: 8px;
font-size: 14px;
border: 1px solid #ececec;
outline: none;
transition: var(--transitionDuration);
color: #6e7071;
}
input[type="text"]:focus,
select:focus {
border: 1px solid #71b2c3;
}
input[name="resource-id-filter"], select[name="resource-type-filter"] {
width: 300px;
border-radius: 3px;
}
h1 {
font-size: 24px;
font-weight: bold;
}
h2 {
font-size: 20px;
font-weight: bold;
}
.heading-title {
display: block;
}
.heading-subtitle {
font-size: 14px;
display: block;
}
.app-content {
padding: 25px;
border-top: 3px solid #71B2C3;
background-color: #ffffff;
box-shadow: 0 0 5px #0000000a;
}
.resource-item {
border-top: 1px solid #ececec;
border-left: 1px solid #ececec;
border-right: 1px solid #ececec;
padding: 15px;
color: #6e7071;
font-size: 14px;
}
.resource-item:last-child {
border-bottom: 1px solid #ececec;
}
.resource-item:hover {
background-color: #f9f9f9;
}
.text--bold {
font-weight: bold;
}
.text--grey {
color: #747578;
}
.text--right {
text-align: right;
}
.card {
padding: 15px;
font-size: 15px;
background: #ffffff;
box-shadow: 0 0 5px #0000000a;
border-radius: 3px;
}
.reset-filter-btn {
border: none;
padding: 8px;
font-size: 14px;
background-color: transparent;
color: #5faabd;
cursor: pointer;
}
/* tabs style */
.tabs-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.tab {
cursor: pointer;
padding: 10px 20px;
margin: 0 2px;
background: transparent;
display: inline-block;
color: #747578;
transition: background-color 200ms;
border-radius: 3px;
}
.tab:hover {
background-color: #f9f9f9;
}
.panels {
width: 100%;
padding: 20px;
}
.panel {
display: none;
animation: fadein .8s;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.panel-title {
font-size: 1.5em;
font-weight: bold
}
.radio {
display: none;
}
#one:checked ~ .panels #one-panel,
#two:checked ~ .panels #two-panel,
#three:checked ~ .panels #three-panel,
#four:checked ~ .panels #four-panel {
display: block
}
#one:checked ~ .tabs #one-tab,
#two:checked ~ .tabs #two-tab,
#three:checked ~ .tabs #three-tab,
#four:checked ~ .tabs #four-tab {
background: #71b2c3;
color: #ffffff;
}
.empty-message-container {
color: #747578;
}</style>
</head>
<body>
<div class="container">
<div class="heading d-flex justify-space-between align-center">
<div>
<h1 class="heading-title mb-1">Driftctl scan report</h1>
<span class="heading-subtitle">Coverage 0%</span>
</div>
<div class="text--right">
<h2 class="heading-date mb-1">Jun 08, 2021</h2>
<span class="heading-subtitle">Scan duration 0s</span>
</div>
</div>
<div class="card d-flex justify-space-around mb-2 text--grey">
<span>Total resources: 0</span>
<span>Managed: 0%</span>
<span>Changed: 0/0</span>
<span>Unmanaged: 0%</span>
<span>Missing: 0%</span>
</div>
<div class="app-content">
<div class="d-flex justify-center mb-2">
<form id="filter-form" action="#">
<input type="text" name="resource-id-filter" placeholder="Search resources..." onkeyup="refreshState()">
<select name="resource-type-filter" onchange="refreshState()">
<option value="">Resource type</option>
</select>
<button type="button" onclick="resetFilters()" class="reset-filter-btn">Reset filters</button>
</form>
</div>
<div class="tabs-wrapper">
<input class="radio" id="one" name="group" type="radio" checked>
<input class="radio" id="two" name="group" type="radio">
<input class="radio" id="three" name="group" type="radio">
<input class="radio" id="four" name="group" type="radio">
<div class="tabs">
</div>
<div class="panels">
</div>
</div>
<div class="empty-message-container mt-5 d-flex justify-center hide">
<p>There's nothing to see there...</p>
</div>
</div>
</div>
</body>
<script lang="js">
const resources = document.querySelectorAll('.resource-item')
function hideResource(res) {
res.classList.add('hide')
}
function displayResource(res) {
res.classList.remove('hide')
}
function resourceIdContains(res, query) {
const el = res.querySelector('.resource-item-id')
if (!el) {
return false
}
return el.innerText.toLowerCase().includes(query.toLowerCase())
}
function resourceTypeEqual(res, type) {
const el = res.querySelector('.resource-item-type')
if (!el) {
return false
}
return el.innerText === type
}
function refreshCounters() {
const counterClassMapping = {
'.resource-item-unmanaged': '.resource-count-unmanaged',
'.resource-item-changed': '.resource-count-changed',
'.resource-item-deleted': '.resource-count-deleted',
'.resource-item-alerts': '.resource-count-alerts',
}
for (const resClass in counterClassMapping) {
const countEl = document.querySelector(counterClassMapping[resClass])
if (!countEl) {
continue
}
countEl.textContent = Array.from(document.querySelectorAll(resClass)).filter(el => !el.classList.contains('hide')).length
}
}
function computeEmptyMessage() {
const msgEl = document.querySelector('.empty-message-container')
const wrapperEl = document.querySelector('.tabs-wrapper')
const count = Array.from(resources).filter(el => !el.classList.contains('hide')).length
if (count === 0) {
msgEl.classList.remove('hide')
wrapperEl.classList.add('hide')
return
}
msgEl.classList.add('hide')
wrapperEl.classList.remove('hide')
}
function refreshState() {
const queryFilterInput = document.querySelector('input[name=resource-id-filter]').value
const typeFilterInput = document.querySelector('select[name=resource-type-filter]').value
for (const res of resources) {
const matchId = !queryFilterInput.length || resourceIdContains(res, queryFilterInput)
const matchType = !typeFilterInput.length || resourceTypeEqual(res, typeFilterInput)
if (matchId && matchType) {
displayResource(res)
continue
}
hideResource(res)
}
refreshCounters()
computeEmptyMessage()
}
function resetFilters() {
document.querySelector('input[name=resource-id-filter]').value = ""
document.querySelector('select[name=resource-type-filter]').value = ""
refreshState()
}
refreshState()
</script>
</html>

View File

@ -0,0 +1,428 @@
<!doctype html>
<html>
<head>
<title>Driftctl scan report</title>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="shortcut icon"
href="https://raw.githubusercontent.com/cloudskiff/driftctl-docs/main/static/img/favicon.ico"/>
<style>/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/*
* Custom style
*/
:root {
--transitionDuration: 400ms;
}
body {
font-family: "Helvetica", sans-serif;
color: #1C1E21;
background-color: #F7F7F9;
padding-bottom: 50px;
}
div.container {
max-width: 100%;
width: 1280px;
margin: auto;
}
.heading {
height: 130px;
}
.hide {
display: none !important;
}
.d-flex {
display: flex;
flex-direction: row;
}
.justify-center {
justify-content: center;
}
.justify-space-around {
justify-content: space-around;
}
.justify-space-between {
justify-content: space-between;
}
.align-center {
align-items: center;
}
.mt-5 {
margin-top: 50px;
}
.mb-5 {
margin-bottom: 50px;
}
.mt-2 {
margin-top: 20px;
}
.mb-2 {
margin-bottom: 20px;
}
.mt-1 {
margin-top: 10px;
}
.mb-1 {
margin-bottom: 10px;
}
.pa-1 {
padding: 10px;
}
.pl-1 {
padding-left: 10px;
}
.pr-1 {
padding-right: 10px;
}
.pt-1 {
padding-top: 10px;
}
.pb-1 {
padding-bottom: 10px;
}
input[type="text"],
select {
padding: 8px;
font-size: 14px;
border: 1px solid #ececec;
outline: none;
transition: var(--transitionDuration);
color: #6e7071;
}
input[type="text"]:focus,
select:focus {
border: 1px solid #71b2c3;
}
input[name="resource-id-filter"], select[name="resource-type-filter"] {
width: 300px;
border-radius: 3px;
}
h1 {
font-size: 24px;
font-weight: bold;
}
h2 {
font-size: 20px;
font-weight: bold;
}
.heading-title {
display: block;
}
.heading-subtitle {
font-size: 14px;
display: block;
}
.app-content {
padding: 25px;
border-top: 3px solid #71B2C3;
background-color: #ffffff;
box-shadow: 0 0 5px #0000000a;
}
.resource-item {
border-top: 1px solid #ececec;
border-left: 1px solid #ececec;
border-right: 1px solid #ececec;
padding: 15px;
color: #6e7071;
font-size: 14px;
}
.resource-item:last-child {
border-bottom: 1px solid #ececec;
}
.resource-item:hover {
background-color: #f9f9f9;
}
.text--bold {
font-weight: bold;
}
.text--grey {
color: #747578;
}
.text--right {
text-align: right;
}
.card {
padding: 15px;
font-size: 15px;
background: #ffffff;
box-shadow: 0 0 5px #0000000a;
border-radius: 3px;
}
.reset-filter-btn {
border: none;
padding: 8px;
font-size: 14px;
background-color: transparent;
color: #5faabd;
cursor: pointer;
}
/* tabs style */
.tabs-wrapper {
display: flex;
flex-direction: column;
align-items: center;
}
.tab {
cursor: pointer;
padding: 10px 20px;
margin: 0 2px;
background: transparent;
display: inline-block;
color: #747578;
transition: background-color 200ms;
border-radius: 3px;
}
.tab:hover {
background-color: #f9f9f9;
}
.panels {
width: 100%;
padding: 20px;
}
.panel {
display: none;
animation: fadein .8s;
}
@keyframes fadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.panel-title {
font-size: 1.5em;
font-weight: bold
}
.radio {
display: none;
}
#one:checked ~ .panels #one-panel,
#two:checked ~ .panels #two-panel,
#three:checked ~ .panels #three-panel,
#four:checked ~ .panels #four-panel {
display: block
}
#one:checked ~ .tabs #one-tab,
#two:checked ~ .tabs #two-tab,
#three:checked ~ .tabs #three-tab,
#four:checked ~ .tabs #four-tab {
background: #71b2c3;
color: #ffffff;
}
.empty-message-container {
color: #747578;
}</style>
</head>
<body>
<div class="container">
<div class="heading d-flex justify-space-between align-center">
<div>
<h1 class="heading-title mb-1">Driftctl scan report</h1>
<span class="heading-subtitle">Coverage 100%</span>
</div>
<div class="text--right">
<h2 class="heading-date mb-1">Jun 08, 2021</h2>
<span class="heading-subtitle">Scan duration 1m12s</span>
</div>
</div>
<div class="card d-flex justify-space-around mb-2 text--grey">
<span>Total resources: 1</span>
<span>Managed: 100%</span>
<span>Changed: 0/1</span>
<span>Unmanaged: 0%</span>
<span>Missing: 0%</span>
</div>
<div class="app-content">
<div class="d-flex justify-center mt-5 mb-5">
<h1>Congrats! Your infrastructure is in sync</h1>
</div>
</div>
</div>
</body>
<script lang="js">
const resources = document.querySelectorAll('.resource-item')
function hideResource(res) {
res.classList.add('hide')
}
function displayResource(res) {
res.classList.remove('hide')
}
function resourceIdContains(res, query) {
const el = res.querySelector('.resource-item-id')
if (!el) {
return false
}
return el.innerText.toLowerCase().includes(query.toLowerCase())
}
function resourceTypeEqual(res, type) {
const el = res.querySelector('.resource-item-type')
if (!el) {
return false
}
return el.innerText === type
}
function refreshCounters() {
const counterClassMapping = {
'.resource-item-unmanaged': '.resource-count-unmanaged',
'.resource-item-changed': '.resource-count-changed',
'.resource-item-deleted': '.resource-count-deleted',
'.resource-item-alerts': '.resource-count-alerts',
}
for (const resClass in counterClassMapping) {
const countEl = document.querySelector(counterClassMapping[resClass])
if (!countEl) {
continue
}
countEl.textContent = Array.from(document.querySelectorAll(resClass)).filter(el => !el.classList.contains('hide')).length
}
}
function computeEmptyMessage() {
const msgEl = document.querySelector('.empty-message-container')
const wrapperEl = document.querySelector('.tabs-wrapper')
const count = Array.from(resources).filter(el => !el.classList.contains('hide')).length
if (count === 0) {
msgEl.classList.remove('hide')
wrapperEl.classList.add('hide')
return
}
msgEl.classList.add('hide')
wrapperEl.classList.remove('hide')
}
function refreshState() {
const queryFilterInput = document.querySelector('input[name=resource-id-filter]').value
const typeFilterInput = document.querySelector('select[name=resource-type-filter]').value
for (const res of resources) {
const matchId = !queryFilterInput.length || resourceIdContains(res, queryFilterInput)
const matchType = !typeFilterInput.length || resourceTypeEqual(res, typeFilterInput)
if (matchId && matchType) {
displayResource(res)
continue
}
hideResource(res)
}
refreshCounters()
computeEmptyMessage()
}
function resetFilters() {
document.querySelector('input[name=resource-id-filter]').value = ""
document.querySelector('select[name=resource-type-filter]').value = ""
refreshState()
}
refreshState()
</script>
</html>