Add frontend to certmon
This commit is contained in:
parent
5beb5ceb44
commit
8222543223
10 changed files with 265 additions and 17 deletions
7
go.mod
7
go.mod
|
@ -5,10 +5,12 @@ go 1.21.4
|
|||
require (
|
||||
git.tdpain.net/codemicro/kindle-dashboard v0.1.2
|
||||
git.tdpain.net/pkg/cfger v0.1.0
|
||||
github.com/a-h/templ v0.2.639
|
||||
github.com/carlmjohnson/requests v0.23.5
|
||||
github.com/go-playground/validator v9.31.0+incompatible
|
||||
github.com/jakobvarmose/go-qidenticon v0.0.0-20170128000056-5c327fb4e74a
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/julienschmidt/httprouter v1.3.0
|
||||
github.com/maragudk/gomponents v0.20.2
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
|
@ -33,13 +35,12 @@ require (
|
|||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/tdewolff/canvas v0.0.0-20231102134958-6de43c767dbf // indirect
|
||||
github.com/tdewolff/minify/v2 v2.20.5 // indirect
|
||||
github.com/tdewolff/parse/v2 v2.7.3 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -6,6 +6,8 @@ git.tdpain.net/pkg/cfger v0.1.0 h1:Yhs2DaFIdbcSrbyywhsoxrHPevDEEBEKbqJqrUa3eso=
|
|||
git.tdpain.net/pkg/cfger v0.1.0/go.mod h1:Kq5/hsUnYSYM2BVGFtXMlYEDIsIYiZTz4MUIlqZeX0k=
|
||||
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f h1:l7moT9o/v/9acCWA64Yz/HDLqjcRTvc0noQACi4MsJw=
|
||||
github.com/ByteArena/poly2tri-go v0.0.0-20170716161910-d102ad91854f/go.mod h1:vIOkSdX3NDCPwgu8FIuTat2zDF0FPXXQ0RYFRy+oQic=
|
||||
github.com/a-h/templ v0.2.639 h1:iNyjh6gllEshVDcj3taqtz7dltPKBtncvP+M8HNGdGQ=
|
||||
github.com/a-h/templ v0.2.639/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
|
@ -57,8 +59,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS
|
|||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/jakobvarmose/go-qidenticon v0.0.0-20170128000056-5c327fb4e74a h1:1aXp5vaXeDYGVzOx20czCIsrjvLX+n+2OIChSS3FN7A=
|
||||
|
@ -75,8 +77,6 @@ github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
|||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/maragudk/gomponents v0.20.1 h1:TeJY1fXEcfUvzmvjeUgxol42dvkYMggK1c0V67crWWs=
|
||||
github.com/maragudk/gomponents v0.20.1/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
|
||||
github.com/maragudk/gomponents v0.20.2 h1:39FhnBNNCJzqNcD9Hmvp/5xj0otweFoyvVgFG6kXoy0=
|
||||
github.com/maragudk/gomponents v0.20.2/go.mod h1:nHkNnZL6ODgMBeJhrZjkMHVvNdoYsfmpKB2/hjdQ0Hg=
|
||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
|
@ -109,8 +109,8 @@ golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
|
|||
golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
|
||||
golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -121,8 +121,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE=
|
||||
|
|
|
@ -7,8 +7,11 @@ import (
|
|||
"github.com/codemicro/platform/platform"
|
||||
"github.com/codemicro/platform/platform/storage"
|
||||
"github.com/codemicro/platform/platform/util"
|
||||
"github.com/codemicro/platform/platform/util/htmlutil"
|
||||
"github.com/jordan-wright/email"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -23,11 +26,31 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
router.GET("/", util.WrapHandler(func(rw http.ResponseWriter, rq *http.Request, _ httprouter.Params) error {
|
||||
|
||||
certs, err := ((*certificate)(nil)).List(db)
|
||||
if err != nil {
|
||||
return fmt.Errorf("list certificates: %w", err)
|
||||
}
|
||||
|
||||
activeCerts, err := renderActiveCertificatesView(certs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return htmlutil.GovUKDesignBasePage(
|
||||
rw,
|
||||
"Certificate Monitor",
|
||||
template.HTML(activeCerts),
|
||||
)
|
||||
}))
|
||||
platform.RegisterProvider(moduleName, router)
|
||||
|
||||
if err := setupDB(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(monitorJob())
|
||||
}
|
||||
|
||||
func monitorJob() error {
|
||||
|
|
|
@ -60,7 +60,8 @@ type certificate struct {
|
|||
URL string `db:"url"`
|
||||
Type certificateType `db:"type"`
|
||||
|
||||
Cert *x509.Certificate `db:"-"`
|
||||
Domains []string `db:"-"`
|
||||
Cert *x509.Certificate `db:"-"`
|
||||
}
|
||||
|
||||
func (cert *certificate) Insert(dbe sqlx.Ext) error {
|
||||
|
@ -80,7 +81,53 @@ func (cert *certificate) Get(dbe sqlx.Queryer) error {
|
|||
)
|
||||
}
|
||||
|
||||
func (cert *certificate) List(dbe sqlx.Queryer) ([]*certificate, error) {
|
||||
type syntheticResult struct {
|
||||
Fingerprint string `db:"fingerprint"`
|
||||
SerialNumber string `db:"serial"`
|
||||
NotBefore time.Time `db:"not_before"`
|
||||
NotAfter time.Time `db:"not_after"`
|
||||
URL string `db:"url"`
|
||||
Type certificateType `db:"type"`
|
||||
DNSName string `db:"dns_name"`
|
||||
}
|
||||
|
||||
var res []*syntheticResult
|
||||
|
||||
err := sqlx.Select(
|
||||
dbe,
|
||||
&res,
|
||||
`SELECT dns_names.dns_name, certificates.fingerprint, serial, not_before, not_after, type, url FROM dns_names JOIN certificates ON dns_names.fingerprint = certificates.fingerprint;`,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list all certificates: %w", err)
|
||||
}
|
||||
|
||||
var mappedRes []*certificate
|
||||
for _, item := range res {
|
||||
mappedRes = append(mappedRes, &certificate{
|
||||
Fingerprint: item.Fingerprint,
|
||||
SerialNumber: item.SerialNumber,
|
||||
NotBefore: item.NotBefore,
|
||||
NotAfter: item.NotAfter,
|
||||
URL: item.URL,
|
||||
Type: item.Type,
|
||||
Domains: []string{item.DNSName},
|
||||
})
|
||||
}
|
||||
|
||||
return mappedRes, nil
|
||||
}
|
||||
|
||||
func (cert *certificate) getAssociatedDomains() []string {
|
||||
if cert.Cert == nil {
|
||||
if len(cert.Domains) != 0 {
|
||||
return cert.Domains
|
||||
}
|
||||
panic("nil cert") // spicy!
|
||||
}
|
||||
|
||||
// What we should do here is slightly unclear - RFC5280 says we should consider both the common name and DNS
|
||||
// alternative names but RFC6125 says we should only consider the common name if there are no DNS names. The
|
||||
// latter is new enough that some CAs don't abide by it and this is so low stakes that I'm just going to stick
|
||||
|
@ -104,14 +151,14 @@ func (cert *certificate) getAssociatedDomains() []string {
|
|||
}
|
||||
|
||||
type dnsName struct {
|
||||
Fingerprint string `db:"serial"`
|
||||
Fingerprint string `db:"fingerprint"`
|
||||
DNSName string `db:"dns_name"`
|
||||
}
|
||||
|
||||
func (dnsn *dnsName) Insert(dbe sqlx.Ext) error {
|
||||
_, err := sqlx.NamedExec(
|
||||
dbe,
|
||||
`INSERT INTO dns_names(serial, dns_name) VALUES(:serial, :dns_name);`,
|
||||
`INSERT INTO dns_names(fingerprint, dns_name) VALUES(:fingerprint, :dns_name);`,
|
||||
dnsn,
|
||||
)
|
||||
return err
|
||||
|
|
|
@ -11,6 +11,7 @@ CREATE TABLE IF NOT EXISTS certificates(
|
|||
---
|
||||
|
||||
CREATE TABLE IF NOT EXISTS dns_names(
|
||||
serial VARCHAR,
|
||||
dns_name VARCHAR
|
||||
fingerprint VARCHAR,
|
||||
dns_name VARCHAR,
|
||||
PRIMARY KEY (fingerprint, dns_name)
|
||||
);
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<div class="govuk-grid-row">
|
||||
<div class="govuk-grid-column-full">
|
||||
<h1 class="govuk-heading-l">Active certificates</h1>
|
||||
<p class="govuk-body">Jump to...</p>
|
||||
<ul class="govuk-list govuk-list--bullet">
|
||||
{{ range . }}
|
||||
<li><a class="govuk-link" href="#prop-{{ .SanitisedProperty }}">{{ .Property }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ range $i, $prop := . }}
|
||||
<h2 class="govuk-heading-m" id="prop-{{ $prop.SanitisedProperty }}">{{ $prop.Property }}</h2>
|
||||
|
||||
<table class="govuk-table">
|
||||
<!-- <thead class="govuk-table__head">-->
|
||||
<!-- <tr class="govuk-table__row">-->
|
||||
<!-- <th scope="col" class="govuk-table__header">Domain</th>-->
|
||||
<!-- <th scope="col" class="govuk-table__header">Expires</th>-->
|
||||
<!-- <th scope="col" class="govuk-table__header">Status</th>-->
|
||||
<!-- <th scope="col" class="govuk-table__header"></th>-->
|
||||
<!-- </tr>-->
|
||||
<!-- </thead>-->
|
||||
<tbody class="govuk-table__body">
|
||||
{{ range $prop.Certificates }}
|
||||
{{ if eq .Type "precertificate" }}
|
||||
{{ continue }}
|
||||
{{ end }}
|
||||
<tr class="govuk-table__row">
|
||||
<th scope="row" class="govuk-table__header">{{ index .Domains 0 }}</th>
|
||||
<td class="govuk-table__cell">{{ .NotAfter.Format "2006-01-02" }}</td>
|
||||
<td class="govuk-table__cell">{{ if isExpired .NotAfter }}<strong class="govuk-tag govuk-tag--red">Expired</strong>{{ else if isExpiringSoon .NotAfter }}<strong class="govuk-tag govuk-tag--yellow">Expiring soon</strong>{{ else }}<strong class="govuk-tag govuk-tag--green">Active</strong>{{ end }}</td>
|
||||
<td class="govuk-table__cell"><a href="{{ .URL }}">Details</a></td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
62
modules/certificateMonitor/views.go
Normal file
62
modules/certificateMonitor/views.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
package certificateMonitor
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"github.com/codemicro/platform/config"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
//go:embed templates
|
||||
var templateFS embed.FS
|
||||
var templates = template.Must(template.New("root").Funcs(template.FuncMap{
|
||||
"isExpiringSoon": func(ref time.Time) bool {
|
||||
return ref.Add(-time.Hour * 24 * 30).Before(time.Now())
|
||||
},
|
||||
"isExpired": func(ref time.Time) bool {
|
||||
return ref.Before(time.Now())
|
||||
},
|
||||
}).ParseFS(fs.FS(templateFS), "templates/*.tpl.html"))
|
||||
|
||||
func renderActiveCertificatesView(certs []*certificate) (string, error) {
|
||||
type propertyCertificates struct {
|
||||
Property string
|
||||
SanitisedProperty string
|
||||
Certificates []*certificate
|
||||
}
|
||||
|
||||
var certGroups []*propertyCertificates
|
||||
{
|
||||
groups := make(map[string]*propertyCertificates)
|
||||
for _, prop := range strings.Split(config.Get().CertificateMonitor.Properties, " ") {
|
||||
groups[strings.ToLower(prop)] = &propertyCertificates{Property: prop, SanitisedProperty: strings.ReplaceAll(prop, ".", "")}
|
||||
}
|
||||
|
||||
for _, cert := range certs {
|
||||
for property, group := range groups {
|
||||
if strings.HasSuffix(cert.getAssociatedDomains()[0], property) {
|
||||
group.Certificates = append(group.Certificates, cert)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, x := range groups {
|
||||
certGroups = append(certGroups, x)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(certGroups, func(i, j int) bool {
|
||||
return strings.ToLower(certGroups[i].Property) < strings.ToLower(certGroups[j].Property)
|
||||
})
|
||||
|
||||
sb := new(strings.Builder)
|
||||
if err := templates.ExecuteTemplate(sb, "activeCertificates.tpl.html", certGroups); err != nil {
|
||||
return "", fmt.Errorf("execute activeCertificates.tpl.html: %w", err)
|
||||
}
|
||||
return sb.String(), nil
|
||||
}
|
53
platform/util/htmlutil/gds.html
Normal file
53
platform/util/htmlutil/gds.html
Normal file
|
@ -0,0 +1,53 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="govuk-template">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{{ .Title }}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
<link rel="stylesheet" href="//{{ .Host }}/main.css">
|
||||
</head>
|
||||
|
||||
<body class="govuk-template__body">
|
||||
<script>
|
||||
document.body.className += ' js-enabled' + ('noModule' in HTMLScriptElement.prototype ? ' govuk-frontend-supported' : '');
|
||||
</script>
|
||||
<a href="#main-content" class="govuk-skip-link" data-module="govuk-skip-link">Skip to main content</a>
|
||||
<header class="govuk-header" role="banner" data-module="govuk-header">
|
||||
<div class="govuk-header__container govuk-width-container">
|
||||
<div class="govuk-header__logo">
|
||||
<a href="/" class="govuk-header__link govuk-header__link--homepage">
|
||||
<span>{{ .Title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<div class="govuk-width-container">
|
||||
<main class="govuk-main-wrapper" id="main-content" role="main">
|
||||
{{ .BodyContent }}
|
||||
</main>
|
||||
</div>
|
||||
<footer class="govuk-footer" role="contentinfo">
|
||||
<div class="govuk-width-container">
|
||||
<div class="govuk-footer__meta">
|
||||
<div class="govuk-footer__meta-item govuk-footer__meta-item--grow">
|
||||
<span class="govuk-footer__licence-description">The design of this site uses the <a href="https://design-system.service.gov.uk" class="govuk-footer__link">GOV.UK design system</a>, which is covered by the
|
||||
<a
|
||||
class="govuk-footer__link"
|
||||
href="https://github.com/alphagov/govuk-frontend/blob/main/LICENSE.txt"
|
||||
rel="license">MIT License</a>.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
<script type="module" src="//{{ .Host }}/govuk-frontend.min.js"></script>
|
||||
<script type="module">
|
||||
import {
|
||||
initAll
|
||||
} from '//{{ .Host }}/govuk-frontend.min.js'
|
||||
initAll()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -1,8 +1,12 @@
|
|||
package htmlutil
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"github.com/codemicro/platform/config"
|
||||
g "github.com/maragudk/gomponents"
|
||||
. "github.com/maragudk/gomponents/html"
|
||||
"html/template"
|
||||
"io"
|
||||
)
|
||||
|
||||
func BasePage(title string, content ...g.Node) g.Node {
|
||||
|
@ -26,3 +30,21 @@ func UnorderedList(x []string) g.Node {
|
|||
return Li(g.Text(s))
|
||||
})...)
|
||||
}
|
||||
|
||||
//go:embed gds.html
|
||||
var rawGovUKTemplate string
|
||||
var govUKTemplate = template.Must(template.New("gds-base").Parse(rawGovUKTemplate))
|
||||
|
||||
func GovUKDesignBasePage(buf io.Writer, title string, content template.HTML) error {
|
||||
type tplData struct {
|
||||
Host string
|
||||
Title string
|
||||
BodyContent template.HTML
|
||||
}
|
||||
|
||||
return govUKTemplate.Execute(buf, &tplData{
|
||||
Host: config.Get().HostSuffix,
|
||||
Title: title,
|
||||
BodyContent: content, // casting to this type prevents escaping
|
||||
})
|
||||
}
|
||||
|
|
|
@ -5,4 +5,5 @@
|
|||
}
|
||||
|
||||
$govuk-font-family: "Inter", sans-serif;
|
||||
@import "../node_modules/govuk-frontend/dist/govuk/all";
|
||||
$govuk-brand-colour: #df3062;
|
||||
@import "../node_modules/govuk-frontend/dist/govuk/all";
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue