forked from mystiq/dex
Merge pull request #711 from ericchiang/themes
*: add theme based frontend configuration
This commit is contained in:
commit
9d9ad4a5b3
16 changed files with 175 additions and 556 deletions
|
@ -13,6 +13,11 @@ RUN apk add --update ca-certificates openssl
|
||||||
|
|
||||||
COPY _output/bin/dex /usr/local/bin/dex
|
COPY _output/bin/dex /usr/local/bin/dex
|
||||||
|
|
||||||
|
# Import frontend assets and set the correct CWD directory so the assets
|
||||||
|
# are in the default path.
|
||||||
|
COPY web /web
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
ENTRYPOINT ["dex"]
|
ENTRYPOINT ["dex"]
|
||||||
|
|
||||||
CMD ["version"]
|
CMD ["version"]
|
||||||
|
|
8
Makefile
8
Makefile
|
@ -25,7 +25,7 @@ LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)"
|
||||||
|
|
||||||
build: bin/dex bin/example-app
|
build: bin/dex bin/example-app
|
||||||
|
|
||||||
bin/dex: FORCE generated
|
bin/dex: FORCE
|
||||||
@go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
@go install -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
||||||
|
|
||||||
bin/example-app: FORCE
|
bin/example-app: FORCE
|
||||||
|
@ -35,9 +35,6 @@ bin/example-app: FORCE
|
||||||
release-binary:
|
release-binary:
|
||||||
@go build -o _output/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
@go build -o _output/bin/dex -v -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
|
||||||
|
|
||||||
.PHONY: generated
|
|
||||||
generated: server/templates_default.go
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test -v -i $(shell go list ./... | grep -v '/vendor/')
|
@go test -v -i $(shell go list ./... | grep -v '/vendor/')
|
||||||
@go test -v $(shell go list ./... | grep -v '/vendor/')
|
@go test -v $(shell go list ./... | grep -v '/vendor/')
|
||||||
|
@ -57,9 +54,6 @@ lint:
|
||||||
golint -set_exit_status $$package $$i || exit 1; \
|
golint -set_exit_status $$package $$i || exit 1; \
|
||||||
done
|
done
|
||||||
|
|
||||||
server/templates_default.go: $(wildcard web/templates/**)
|
|
||||||
@go run server/templates_default_gen.go
|
|
||||||
|
|
||||||
_output/bin/dex:
|
_output/bin/dex:
|
||||||
# Using rkt to build the dex binary.
|
# Using rkt to build the dex binary.
|
||||||
@./scripts/rkt-build
|
@./scripts/rkt-build
|
||||||
|
|
|
@ -30,7 +30,7 @@ type Config struct {
|
||||||
GRPC GRPC `json:"grpc"`
|
GRPC GRPC `json:"grpc"`
|
||||||
Expiry Expiry `json:"expiry"`
|
Expiry Expiry `json:"expiry"`
|
||||||
|
|
||||||
Templates server.TemplateConfig `json:"templates"`
|
Frontend server.WebConfig `json:"frontend"`
|
||||||
|
|
||||||
// StaticClients cause the server to use this list of clients rather than
|
// StaticClients cause the server to use this list of clients rather than
|
||||||
// querying the storage. Write operations, like creating a client, will fail.
|
// querying the storage. Write operations, like creating a client, will fail.
|
||||||
|
|
|
@ -151,7 +151,7 @@ func serve(cmd *cobra.Command, args []string) error {
|
||||||
Issuer: c.Issuer,
|
Issuer: c.Issuer,
|
||||||
Connectors: connectors,
|
Connectors: connectors,
|
||||||
Storage: s,
|
Storage: s,
|
||||||
TemplateConfig: c.Templates,
|
Web: c.Frontend,
|
||||||
EnablePasswordDB: c.EnablePasswordDB,
|
EnablePasswordDB: c.EnablePasswordDB,
|
||||||
}
|
}
|
||||||
if c.Expiry.SigningKeys != "" {
|
if c.Expiry.SigningKeys != "" {
|
||||||
|
|
|
@ -14,7 +14,7 @@ storage:
|
||||||
|
|
||||||
# Configuration for the HTTP endpoints.
|
# Configuration for the HTTP endpoints.
|
||||||
web:
|
web:
|
||||||
http: 127.0.0.1:5556
|
http: 0.0.0.0:5556
|
||||||
# Uncomment for HTTPS options.
|
# Uncomment for HTTPS options.
|
||||||
# https: 127.0.0.1:5554
|
# https: 127.0.0.1:5554
|
||||||
# tlsCert: /etc/dex/tls.crt
|
# tlsCert: /etc/dex/tls.crt
|
||||||
|
|
|
@ -56,7 +56,32 @@ type Config struct {
|
||||||
|
|
||||||
EnablePasswordDB bool
|
EnablePasswordDB bool
|
||||||
|
|
||||||
TemplateConfig TemplateConfig
|
Web WebConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebConfig holds the server's frontend templates and asset configuration.
|
||||||
|
//
|
||||||
|
// These are currently very custom to CoreOS and it's not recommended that
|
||||||
|
// outside users attempt to customize these.
|
||||||
|
type WebConfig struct {
|
||||||
|
// A filepath to web static.
|
||||||
|
//
|
||||||
|
// It is expected to contain the following directories:
|
||||||
|
//
|
||||||
|
// * static - Static static served at "( issuer URL )/static".
|
||||||
|
// * templates - HTML templates controlled by dex.
|
||||||
|
// * themes/(theme) - Static static served at "( issuer URL )/theme".
|
||||||
|
//
|
||||||
|
Dir string
|
||||||
|
|
||||||
|
// Defaults to "( issuer URL )/theme/logo.png"
|
||||||
|
LogoURL string
|
||||||
|
|
||||||
|
// Defaults to "dex"
|
||||||
|
Issuer string
|
||||||
|
|
||||||
|
// Defaults to "coreos"
|
||||||
|
Theme string
|
||||||
}
|
}
|
||||||
|
|
||||||
func value(val, defaultValue time.Duration) time.Duration {
|
func value(val, defaultValue time.Duration) time.Duration {
|
||||||
|
@ -130,9 +155,17 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
||||||
supported[respType] = true
|
supported[respType] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
tmpls, err := loadTemplates(c.TemplateConfig)
|
web := webConfig{
|
||||||
|
dir: c.Web.Dir,
|
||||||
|
logoURL: c.Web.LogoURL,
|
||||||
|
issuerURL: c.Issuer,
|
||||||
|
issuer: c.Web.Issuer,
|
||||||
|
theme: c.Web.Theme,
|
||||||
|
}
|
||||||
|
|
||||||
|
static, theme, tmpls, err := loadWebConfig(web)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("server: failed to load templates: %v", err)
|
return nil, fmt.Errorf("server: failed to load web static: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
now := c.Now
|
now := c.Now
|
||||||
|
@ -159,6 +192,10 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
||||||
handleFunc := func(p string, h http.HandlerFunc) {
|
handleFunc := func(p string, h http.HandlerFunc) {
|
||||||
r.HandleFunc(path.Join(issuerURL.Path, p), h)
|
r.HandleFunc(path.Join(issuerURL.Path, p), h)
|
||||||
}
|
}
|
||||||
|
handlePrefix := func(p string, h http.Handler) {
|
||||||
|
prefix := path.Join(issuerURL.Path, p)
|
||||||
|
r.PathPrefix(prefix).Handler(http.StripPrefix(prefix, h))
|
||||||
|
}
|
||||||
r.NotFoundHandler = http.HandlerFunc(s.notFound)
|
r.NotFoundHandler = http.HandlerFunc(s.notFound)
|
||||||
|
|
||||||
discoveryHandler, err := s.discoveryHandler()
|
discoveryHandler, err := s.discoveryHandler()
|
||||||
|
@ -175,6 +212,8 @@ func newServer(ctx context.Context, c Config, rotationStrategy rotationStrategy)
|
||||||
handleFunc("/callback", s.handleConnectorCallback)
|
handleFunc("/callback", s.handleConnectorCallback)
|
||||||
handleFunc("/approval", s.handleApproval)
|
handleFunc("/approval", s.handleApproval)
|
||||||
handleFunc("/healthz", s.handleHealth)
|
handleFunc("/healthz", s.handleHealth)
|
||||||
|
handlePrefix("/static", static)
|
||||||
|
handlePrefix("/theme", theme)
|
||||||
s.mux = r
|
s.mux = r
|
||||||
|
|
||||||
startKeyRotation(ctx, c.Storage, rotationStrategy, now)
|
startKeyRotation(ctx, c.Storage, rotationStrategy, now)
|
||||||
|
|
|
@ -11,6 +11,8 @@ import (
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -85,6 +87,9 @@ func newTestServer(ctx context.Context, t *testing.T, updateConfig func(c *Confi
|
||||||
Connector: mock.NewCallbackConnector(),
|
Connector: mock.NewCallbackConnector(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Web: WebConfig{
|
||||||
|
Dir: filepath.Join(os.Getenv("GOPATH"), "src/github.com/coreos/dex/web"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if updateConfig != nil {
|
if updateConfig != nil {
|
||||||
updateConfig(&config)
|
updateConfig(&config)
|
||||||
|
|
|
@ -6,8 +6,10 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,8 +20,6 @@ const (
|
||||||
tmplOOB = "oob.html"
|
tmplOOB = "oob.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
const coreOSLogoURL = "https://coreos.com/assets/images/brand/coreos-wordmark-135x40px.png"
|
|
||||||
|
|
||||||
var requiredTmpls = []string{
|
var requiredTmpls = []string{
|
||||||
tmplApproval,
|
tmplApproval,
|
||||||
tmplLogin,
|
tmplLogin,
|
||||||
|
@ -27,65 +27,122 @@ var requiredTmpls = []string{
|
||||||
tmplOOB,
|
tmplOOB,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TemplateConfig describes.
|
type templates struct {
|
||||||
type TemplateConfig struct {
|
loginTmpl *template.Template
|
||||||
// TODO(ericchiang): Asking for a directory with a set of templates doesn't indicate
|
approvalTmpl *template.Template
|
||||||
// what the templates should look like and doesn't allow consumers of this package to
|
passwordTmpl *template.Template
|
||||||
// provide their own templates in memory. In the future clean this up.
|
oobTmpl *template.Template
|
||||||
|
|
||||||
// Directory of the templates. If empty, these will be loaded from memory.
|
|
||||||
Dir string `yaml:"dir"`
|
|
||||||
|
|
||||||
// Defaults to the CoreOS logo and "dex".
|
|
||||||
LogoURL string `yaml:"logoURL"`
|
|
||||||
Issuer string `yaml:"issuerName"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type globalData struct {
|
type webConfig struct {
|
||||||
LogoURL string
|
dir string
|
||||||
Issuer string
|
logoURL string
|
||||||
|
issuer string
|
||||||
|
theme string
|
||||||
|
issuerURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadTemplates(config TemplateConfig) (*templates, error) {
|
func join(base, path string) string {
|
||||||
var tmpls *template.Template
|
b := strings.HasSuffix(base, "/")
|
||||||
if config.Dir != "" {
|
p := strings.HasPrefix(path, "/")
|
||||||
files, err := ioutil.ReadDir(config.Dir)
|
switch {
|
||||||
if err != nil {
|
case b && p:
|
||||||
return nil, fmt.Errorf("read dir: %v", err)
|
return base + path[1:]
|
||||||
|
case b || p:
|
||||||
|
return base + path
|
||||||
|
default:
|
||||||
|
return base + "/" + path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dirExists(dir string) error {
|
||||||
|
stat, err := os.Stat(dir)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return fmt.Errorf("directory %q does not exist", dir)
|
||||||
}
|
}
|
||||||
filenames := []string{}
|
return fmt.Errorf("stat directory %q: %v", dir, err)
|
||||||
for _, file := range files {
|
}
|
||||||
if file.IsDir() {
|
if !stat.IsDir() {
|
||||||
continue
|
return fmt.Errorf("path %q is a file not a directory", dir)
|
||||||
}
|
}
|
||||||
filenames = append(filenames, filepath.Join(config.Dir, file.Name()))
|
return nil
|
||||||
}
|
}
|
||||||
if len(filenames) == 0 {
|
|
||||||
return nil, fmt.Errorf("no files in template dir %s", config.Dir)
|
// loadWebConfig returns static assets, theme assets, and templates used by the frontend by
|
||||||
}
|
// reading the directory specified in the webConfig.
|
||||||
if tmpls, err = template.ParseFiles(filenames...); err != nil {
|
//
|
||||||
return nil, fmt.Errorf("parse files: %v", err)
|
// The directory layout is expected to be:
|
||||||
}
|
//
|
||||||
} else {
|
// ( web directory )
|
||||||
// Load templates from memory. This code is largely copied from the standard library's
|
// |- static
|
||||||
// ParseFiles source code.
|
// |- themes
|
||||||
// See: https://goo.gl/6Wm4mN
|
// | |- (theme name)
|
||||||
for name, data := range defaultTemplates {
|
// |- templates
|
||||||
var t *template.Template
|
//
|
||||||
if tmpls == nil {
|
func loadWebConfig(c webConfig) (static, theme http.Handler, templates *templates, err error) {
|
||||||
tmpls = template.New(name)
|
if c.theme == "" {
|
||||||
}
|
c.theme = "coreos"
|
||||||
if name == tmpls.Name() {
|
}
|
||||||
t = tmpls
|
if c.issuer == "" {
|
||||||
} else {
|
c.issuer = "dex"
|
||||||
t = tmpls.New(name)
|
}
|
||||||
}
|
if c.dir == "" {
|
||||||
if _, err := t.Parse(data); err != nil {
|
c.dir = "./web"
|
||||||
return nil, fmt.Errorf("parsing %s: %v", name, err)
|
}
|
||||||
}
|
if c.logoURL == "" {
|
||||||
|
c.logoURL = join(c.issuerURL, "theme/logo.png")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dirExists(c.dir); err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("load web dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
staticDir := filepath.Join(c.dir, "static")
|
||||||
|
templatesDir := filepath.Join(c.dir, "templates")
|
||||||
|
themeDir := filepath.Join(c.dir, "themes", c.theme)
|
||||||
|
|
||||||
|
for _, dir := range []string{staticDir, templatesDir, themeDir} {
|
||||||
|
if err := dirExists(dir); err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("load dir: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static = http.FileServer(http.Dir(staticDir))
|
||||||
|
theme = http.FileServer(http.Dir(themeDir))
|
||||||
|
|
||||||
|
templates, err = loadTemplates(c, templatesDir)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadTemplates parses the expected templates from the provided directory.
|
||||||
|
func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
|
||||||
|
files, err := ioutil.ReadDir(templatesDir)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filenames := []string{}
|
||||||
|
for _, file := range files {
|
||||||
|
if file.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
filenames = append(filenames, filepath.Join(templatesDir, file.Name()))
|
||||||
|
}
|
||||||
|
if len(filenames) == 0 {
|
||||||
|
return nil, fmt.Errorf("no files in template dir %q", templatesDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs := map[string]interface{}{
|
||||||
|
"issuer": func() string { return c.issuer },
|
||||||
|
"logo": func() string { return c.logoURL },
|
||||||
|
"url": func(s string) string { return join(c.issuerURL, s) },
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpls, err := template.New("").Funcs(funcs).ParseFiles(filenames...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse files: %v", err)
|
||||||
|
}
|
||||||
missingTmpls := []string{}
|
missingTmpls := []string{}
|
||||||
for _, tmplName := range requiredTmpls {
|
for _, tmplName := range requiredTmpls {
|
||||||
if tmpls.Lookup(tmplName) == nil {
|
if tmpls.Lookup(tmplName) == nil {
|
||||||
|
@ -95,16 +152,7 @@ func loadTemplates(config TemplateConfig) (*templates, error) {
|
||||||
if len(missingTmpls) > 0 {
|
if len(missingTmpls) > 0 {
|
||||||
return nil, fmt.Errorf("missing template(s): %s", missingTmpls)
|
return nil, fmt.Errorf("missing template(s): %s", missingTmpls)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.LogoURL == "" {
|
|
||||||
config.LogoURL = coreOSLogoURL
|
|
||||||
}
|
|
||||||
if config.Issuer == "" {
|
|
||||||
config.Issuer = "dex"
|
|
||||||
}
|
|
||||||
|
|
||||||
return &templates{
|
return &templates{
|
||||||
globalData: config,
|
|
||||||
loginTmpl: tmpls.Lookup(tmplLogin),
|
loginTmpl: tmpls.Lookup(tmplLogin),
|
||||||
approvalTmpl: tmpls.Lookup(tmplApproval),
|
approvalTmpl: tmpls.Lookup(tmplApproval),
|
||||||
passwordTmpl: tmpls.Lookup(tmplPassword),
|
passwordTmpl: tmpls.Lookup(tmplPassword),
|
||||||
|
@ -118,14 +166,6 @@ var scopeDescriptions = map[string]string{
|
||||||
"email": "View your email",
|
"email": "View your email",
|
||||||
}
|
}
|
||||||
|
|
||||||
type templates struct {
|
|
||||||
globalData TemplateConfig
|
|
||||||
loginTmpl *template.Template
|
|
||||||
approvalTmpl *template.Template
|
|
||||||
passwordTmpl *template.Template
|
|
||||||
oobTmpl *template.Template
|
|
||||||
}
|
|
||||||
|
|
||||||
type connectorInfo struct {
|
type connectorInfo struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
|
@ -142,21 +182,19 @@ func (t *templates) login(w http.ResponseWriter, connectors []connectorInfo, aut
|
||||||
sort.Sort(byName(connectors))
|
sort.Sort(byName(connectors))
|
||||||
|
|
||||||
data := struct {
|
data := struct {
|
||||||
TemplateConfig
|
|
||||||
Connectors []connectorInfo
|
Connectors []connectorInfo
|
||||||
AuthReqID string
|
AuthReqID string
|
||||||
}{t.globalData, connectors, authReqID}
|
}{connectors, authReqID}
|
||||||
renderTemplate(w, t.loginTmpl, data)
|
renderTemplate(w, t.loginTmpl, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templates) password(w http.ResponseWriter, authReqID, callback, lastUsername string, lastWasInvalid bool) {
|
func (t *templates) password(w http.ResponseWriter, authReqID, callback, lastUsername string, lastWasInvalid bool) {
|
||||||
data := struct {
|
data := struct {
|
||||||
TemplateConfig
|
|
||||||
AuthReqID string
|
AuthReqID string
|
||||||
PostURL string
|
PostURL string
|
||||||
Username string
|
Username string
|
||||||
Invalid bool
|
Invalid bool
|
||||||
}{t.globalData, authReqID, callback, lastUsername, lastWasInvalid}
|
}{authReqID, string(callback), lastUsername, lastWasInvalid}
|
||||||
renderTemplate(w, t.passwordTmpl, data)
|
renderTemplate(w, t.passwordTmpl, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,20 +208,18 @@ func (t *templates) approval(w http.ResponseWriter, authReqID, username, clientN
|
||||||
}
|
}
|
||||||
sort.Strings(accesses)
|
sort.Strings(accesses)
|
||||||
data := struct {
|
data := struct {
|
||||||
TemplateConfig
|
|
||||||
User string
|
User string
|
||||||
Client string
|
Client string
|
||||||
AuthReqID string
|
AuthReqID string
|
||||||
Scopes []string
|
Scopes []string
|
||||||
}{t.globalData, username, clientName, authReqID, accesses}
|
}{username, clientName, authReqID, accesses}
|
||||||
renderTemplate(w, t.approvalTmpl, data)
|
renderTemplate(w, t.approvalTmpl, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *templates) oob(w http.ResponseWriter, code string) {
|
func (t *templates) oob(w http.ResponseWriter, code string) {
|
||||||
data := struct {
|
data := struct {
|
||||||
TemplateConfig
|
|
||||||
Code string
|
Code string
|
||||||
}{t.globalData, code}
|
}{code}
|
||||||
renderTemplate(w, t.oobTmpl, data)
|
renderTemplate(w, t.oobTmpl, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,85 +0,0 @@
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ignoreFile uses "git check-ignore" to determine if we should ignore a file.
|
|
||||||
func ignoreFile(p string) (ok bool, err error) {
|
|
||||||
err = exec.Command("git", "check-ignore", p).Run()
|
|
||||||
if err == nil {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
exitErr, ok := err.(*exec.ExitError)
|
|
||||||
if ok {
|
|
||||||
if sys := exitErr.Sys(); sys != nil {
|
|
||||||
e, ok := sys.(interface {
|
|
||||||
// Is the returned value something that returns an exit status?
|
|
||||||
ExitStatus() int
|
|
||||||
})
|
|
||||||
if ok && e.ExitStatus() == 1 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maps aren't deterministic, use a struct instead.
|
|
||||||
|
|
||||||
type fileData struct {
|
|
||||||
name string
|
|
||||||
data string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// ReadDir guarentees result in sorted order.
|
|
||||||
dir, err := ioutil.ReadDir("web/templates")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
files := []fileData{}
|
|
||||||
for _, file := range dir {
|
|
||||||
p := filepath.Join("web/templates", file.Name())
|
|
||||||
ignore, err := ignoreFile(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if ignore {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(p)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if bytes.Contains(data, []byte{'`'}) {
|
|
||||||
log.Fatalf("file %s contains escape character '`' and cannot be compiled into go source", p)
|
|
||||||
}
|
|
||||||
files = append(files, fileData{file.Name(), string(data)})
|
|
||||||
}
|
|
||||||
|
|
||||||
f := new(bytes.Buffer)
|
|
||||||
|
|
||||||
fmt.Fprintln(f, "// This file was generated by the makefile. Do not edit.")
|
|
||||||
fmt.Fprintln(f)
|
|
||||||
fmt.Fprintln(f, "package server")
|
|
||||||
fmt.Fprintln(f)
|
|
||||||
fmt.Fprintln(f, "// defaultTemplates is a key for file name to file data of the files in web/templates.")
|
|
||||||
fmt.Fprintln(f, "var defaultTemplates = map[string]string{")
|
|
||||||
for _, file := range files {
|
|
||||||
fmt.Fprintf(f, "\t%q: `%s`,\n", file.name, file.data)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f, "}")
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile("server/templates_default.go", f.Bytes(), 0644); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +1 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestNewTemplates(t *testing.T) {
|
|
||||||
var config TemplateConfig
|
|
||||||
if _, err := loadTemplates(config); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadTemplates(t *testing.T) {
|
|
||||||
var config TemplateConfig
|
|
||||||
|
|
||||||
config.Dir = "../web/templates"
|
|
||||||
}
|
|
||||||
|
|
0
web/static/main.css
Normal file
0
web/static/main.css
Normal file
|
@ -3,8 +3,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||||
<title>{{ .Issuer }}</title>
|
<title>{{ issuer }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="{{ url "static/main.css" }}" rel="stylesheet">
|
||||||
|
<link href="{{ url "theme/style.css" }}" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
|
@ -232,7 +234,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="navbar">
|
<div id="navbar">
|
||||||
<div id="navbar-logo-wrap">
|
<div id="navbar-logo-wrap">
|
||||||
<img id="navbar-logo" src="{{ .LogoURL }}">
|
<img id="navbar-logo" src="{{ logo }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2 class="heading">Log in to {{ .Issuer }} </h2>
|
<h2 class="heading">Log in to {{ issuer }} </h2>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{ range $c := .Connectors }}
|
{{ range $c := .Connectors }}
|
||||||
|
|
BIN
web/themes/coreos/logo.png
Normal file
BIN
web/themes/coreos/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
0
web/themes/coreos/style.css
Normal file
0
web/themes/coreos/style.css
Normal file
Loading…
Reference in a new issue