Commit 058de90d authored by Eric Chiang's avatar Eric Chiang Committed by GitHub

Merge pull request #567 from ericchiang/dev-html-templates

dev branch: port templates from master branch
parents 144fa41a 608d8ba9
......@@ -19,7 +19,7 @@ GOARCH=$(shell go env GOARCH)
build: bin/dex bin/example-app
bin/dex: FORCE
bin/dex: FORCE server/templates_default.go
@go install -ldflags $(LD_FLAGS) $(REPO_PATH)/cmd/dex
bin/example-app: FORCE
......@@ -42,6 +42,9 @@ lint:
golint $$package; \
done
server/templates_default.go: $(wildcard web/templates/**)
@go run server/templates_default_gen.go
.PHONY: docker-build
docker-build: bin/dex
@docker build -t $(DOCKER_IMAGE) .
......
......@@ -8,6 +8,7 @@ import (
"github.com/coreos/dex/connector/ldap"
"github.com/coreos/dex/connector/mock"
"github.com/coreos/dex/connector/oidc"
"github.com/coreos/dex/server"
"github.com/coreos/dex/storage"
"github.com/coreos/dex/storage/kubernetes"
"github.com/coreos/dex/storage/memory"
......@@ -21,6 +22,8 @@ type Config struct {
Web Web `yaml:"web"`
OAuth2 OAuth2 `yaml:"oauth2"`
Templates server.TemplateConfig `yaml:"templates"`
StaticClients []storage.Client `yaml:"staticClients"`
}
......@@ -111,9 +114,15 @@ func (c *Connector) UnmarshalYAML(unmarshal func(interface{}) error) error {
var err error
switch c.Type {
case "mock":
case "mockCallback":
var config struct {
Config mock.CallbackConfig `yaml:"config"`
}
err = unmarshal(&config)
c.Config = &config.Config
case "mockPassword":
var config struct {
Config mock.Config `yaml:"config"`
Config mock.PasswordConfig `yaml:"config"`
}
err = unmarshal(&config)
c.Config = &config.Config
......
......@@ -89,11 +89,11 @@ func serve(cmd *cobra.Command, args []string) error {
}
serverConfig := server.Config{
Issuer: c.Issuer,
Connectors: connectors,
Storage: s,
SupportedResponseTypes: c.OAuth2.ResponseTypes,
Issuer: c.Issuer,
Connectors: connectors,
Storage: s,
TemplateConfig: c.Templates,
}
serv, err := server.New(serverConfig)
......
// Package mock implements a mock connector which requires no user interaction.
// Package mock implements connectors which help test various server components.
package mock
import (
......@@ -11,22 +11,24 @@ import (
"github.com/coreos/dex/connector"
)
// New returns a mock connector which requires no user interaction. It always returns
// NewCallbackConnector returns a mock connector which requires no user interaction. It always returns
// the same (fake) identity.
func New() connector.Connector {
return mockConnector{}
func NewCallbackConnector() connector.Connector {
return callbackConnector{}
}
var (
_ connector.CallbackConnector = mockConnector{}
_ connector.GroupsConnector = mockConnector{}
_ connector.CallbackConnector = callbackConnector{}
_ connector.GroupsConnector = callbackConnector{}
_ connector.PasswordConnector = passwordConnector{}
)
type mockConnector struct{}
type callbackConnector struct{}
func (m mockConnector) Close() error { return nil }
func (m callbackConnector) Close() error { return nil }
func (m mockConnector) LoginURL(callbackURL, state string) (string, error) {
func (m callbackConnector) LoginURL(callbackURL, state string) (string, error) {
u, err := url.Parse(callbackURL)
if err != nil {
return "", fmt.Errorf("failed to parse callbackURL %q: %v", callbackURL, err)
......@@ -39,7 +41,7 @@ func (m mockConnector) LoginURL(callbackURL, state string) (string, error) {
var connectorData = []byte("foobar")
func (m mockConnector) HandleCallback(r *http.Request) (connector.Identity, string, error) {
func (m callbackConnector) HandleCallback(r *http.Request) (connector.Identity, string, error) {
return connector.Identity{
UserID: "0-385-28089-0",
Username: "Kilgore Trout",
......@@ -49,17 +51,54 @@ func (m mockConnector) HandleCallback(r *http.Request) (connector.Identity, stri
}, r.URL.Query().Get("state"), nil
}
func (m mockConnector) Groups(identity connector.Identity) ([]string, error) {
func (m callbackConnector) Groups(identity connector.Identity) ([]string, error) {
if !bytes.Equal(identity.ConnectorData, connectorData) {
return nil, errors.New("connector data mismatch")
}
return []string{"authors"}, nil
}
// Config holds the configuration parameters for the mock connector.
type Config struct{}
// CallbackConfig holds the configuration parameters for a connector which requires no interaction.
type CallbackConfig struct{}
// Open returns an authentication strategy which requires no user interaction.
func (c *Config) Open() (connector.Connector, error) {
return New(), nil
func (c *CallbackConfig) Open() (connector.Connector, error) {
return NewCallbackConnector(), nil
}
// PasswordConfig holds the configuration for a mock connector which prompts for the supplied
// username and password.
type PasswordConfig struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
}
// Open returns an authentication strategy which prompts for a predefined username and password.
func (c *PasswordConfig) Open() (connector.Connector, error) {
if c.Username == "" {
return nil, errors.New("no username supplied")
}
if c.Password == "" {
return nil, errors.New("no password supplied")
}
return &passwordConnector{c.Username, c.Password}, nil
}
type passwordConnector struct {
username string
password string
}
func (p passwordConnector) Close() error { return nil }
func (p passwordConnector) Login(username, password string) (identity connector.Identity, validPassword bool, err error) {
if username == p.username && password == p.password {
return connector.Identity{
UserID: "0-385-28089-0",
Username: "Kilgore Trout",
Email: "kilgore@kilgore.trout",
EmailVerified: true,
}, true, nil
}
return identity, false, nil
}
......@@ -7,25 +7,15 @@ web:
http: 127.0.0.1:5556
connectors:
- type: mock
id: mock
- type: mockCallback
id: mock-callback
name: Mock
- type: github
id: github
name: GitHub
- type: mockPassword
id: mock-password
name: Password
config:
clientID: "$GITHUB_CLIENT_ID"
clientSecret: "$GITHUB_CLIENT_SECRET"
redirectURI: http://127.0.0.1:5556/callback/github
org: kubernetes
- type: oidc
id: google
name: Google Account
config:
issuer: https://accounts.google.com
clientID: "$GOOGLE_OAUTH2_CLIENT_ID"
clientSecret: "$GOOGLE_OAUTH2_CLIENT_SECRET"
redirectURI: http://127.0.0.1:5556/callback/google
username: "admin"
password: "PASSWORD"
staticClients:
- id: example-app
......
......@@ -129,15 +129,16 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
connectorInfos := make([]connectorInfo, len(s.connectors))
i := 0
for id := range s.connectors {
for id, conn := range s.connectors {
connectorInfos[i] = connectorInfo{
DisplayName: id,
URL: s.absPath("/auth", id),
ID: id,
Name: conn.DisplayName,
URL: s.absPath("/auth", id),
}
i++
}
renderLoginOptions(w, connectorInfos, state)
s.templates.login(w, connectorInfos, state)
}
func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
......@@ -163,7 +164,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
}
http.Redirect(w, r, callbackURL, http.StatusFound)
case connector.PasswordConnector:
renderPasswordTmpl(w, state, r.URL.String(), "")
s.templates.password(w, state, r.URL.String(), "", false)
default:
s.notFound(w, r)
}
......@@ -174,7 +175,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
return
}
username := r.FormValue("username")
username := r.FormValue("login")
password := r.FormValue("password")
identity, ok, err := passwordConnector.Login(username, password)
......@@ -184,7 +185,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
return
}
if !ok {
renderPasswordTmpl(w, state, r.URL.String(), "Invalid credentials")
s.templates.password(w, state, r.URL.String(), username, true)
return
}
redirectURL, err := s.finalizeLogin(identity, state, connID, conn.Connector)
......@@ -299,7 +300,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
s.renderError(w, http.StatusInternalServerError, errServerError, "")
return
}
renderApprovalTmpl(w, authReq.ID, *authReq.Claims, client, authReq.Scopes)
s.templates.approval(w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes)
case "POST":
if r.FormValue("approval") != "approve" {
s.renderError(w, http.StatusInternalServerError, "approval rejected", "")
......
......@@ -43,6 +43,8 @@ type Config struct {
// If specified, the server will use this function for determining time.
Now func() time.Time
TemplateConfig TemplateConfig
}
func value(val, defaultValue time.Duration) time.Duration {
......@@ -63,6 +65,8 @@ type Server struct {
mux http.Handler
templates *templates
// If enabled, don't prompt user for approval after logging in through connector.
// No package level API to set this, only used in tests.
skipApproval bool
......@@ -107,6 +111,11 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
supported[respType] = true
}
tmpls, err := loadTemplates(c.TemplateConfig)
if err != nil {
return nil, fmt.Errorf("server: failed to load templates: %v", err)
}
now := c.Now
if now == nil {
now = time.Now
......@@ -124,6 +133,7 @@ func newServer(c Config, rotationStrategy rotationStrategy) (*Server, error) {
supportedResponseTypes: supported,
idTokensValidFor: value(c.IDTokensValidFor, 24*time.Hour),
now: now,
templates: tmpls,
}
for _, conn := range c.Connectors {
......
......@@ -64,7 +64,7 @@ FDWV28nTP9sqbtsmU8Tem2jzMvZ7C/Q0AuDoKELFUpux8shm8wfIhyaPnXUGZoAZ
Np4vUwMSYV5mopESLWOg3loBxKyLGFtgGKVCjGiQvy6zISQ4fQo=
-----END RSA PRIVATE KEY-----`)
func newTestServer(updateConfig func(c *Config)) (*httptest.Server, *Server) {
func newTestServer(t *testing.T, updateConfig func(c *Config)) (*httptest.Server, *Server) {
var server *Server
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
server.ServeHTTP(w, r)
......@@ -76,7 +76,7 @@ func newTestServer(updateConfig func(c *Config)) (*httptest.Server, *Server) {
{
ID: "mock",
DisplayName: "Mock",
Connector: mock.New(),
Connector: mock.NewCallbackConnector(),
},
},
}
......@@ -87,21 +87,21 @@ func newTestServer(updateConfig func(c *Config)) (*httptest.Server, *Server) {
var err error
if server, err = newServer(config, staticRotationStrategy(testKey)); err != nil {
panic(err)
t.Fatal(err)
}
server.skipApproval = true // Don't prompt for approval, just immediately redirect with code.
return s, server
}
func TestNewTestServer(t *testing.T) {
newTestServer(nil)
newTestServer(t, nil)
}
func TestDiscovery(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, _ := newTestServer(func(c *Config) {
httpServer, _ := newTestServer(t, func(c *Config) {
c.Issuer = c.Issuer + "/non-root-path"
})
defer httpServer.Close()
......@@ -129,7 +129,7 @@ func TestOAuth2CodeFlow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, s := newTestServer(func(c *Config) {
httpServer, s := newTestServer(t, func(c *Config) {
c.Issuer = c.Issuer + "/non-root-path"
})
defer httpServer.Close()
......@@ -255,7 +255,7 @@ func TestOAuth2ImplicitFlow(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpServer, s := newTestServer(func(c *Config) {
httpServer, s := newTestServer(t, func(c *Config) {
// Enable support for the implicit flow.
c.SupportedResponseTypes = []string{"code", "token"}
})
......
package server
import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"sort"
"text/template"
)
"github.com/coreos/dex/storage"
const (
tmplApproval = "approval.html"
tmplLogin = "login.html"
tmplPassword = "password.html"
)
const coreOSLogoURL = "https://coreos.com/assets/images/brand/coreos-wordmark-135x40px.png"
var requiredTmpls = []string{
tmplApproval,
tmplLogin,
tmplPassword,
}
// TemplateConfig describes.
type TemplateConfig struct {
// 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 {
LogoURL string
Issuer string
}
func loadTemplates(config TemplateConfig) (*templates, error) {
var tmpls *template.Template
if config.Dir != "" {
files, err := ioutil.ReadDir(config.Dir)
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(config.Dir, file.Name()))
}
if len(filenames) == 0 {
return nil, fmt.Errorf("no files in template dir %s", config.Dir)
}
if tmpls, err = template.ParseFiles(filenames...); err != nil {
return nil, fmt.Errorf("parse files: %v", err)
}
} else {
// Load templates from memory. This code is largely copied from the standard library's
// ParseFiles source code.
// See: https://goo.gl/6Wm4mN
for name, data := range defaultTemplates {
var t *template.Template
if tmpls == nil {
tmpls = template.New(name)
}
if name == tmpls.Name() {
t = tmpls
} else {
t = tmpls.New(name)
}
if _, err := t.Parse(data); err != nil {
return nil, fmt.Errorf("parsing %s: %v", name, err)
}
}
}
missingTmpls := []string{}
for _, tmplName := range requiredTmpls {
if tmpls.Lookup(tmplName) == nil {
missingTmpls = append(missingTmpls, tmplName)
}
}
if len(missingTmpls) > 0 {
return nil, fmt.Errorf("missing template(s): %s", missingTmpls)
}
if config.LogoURL == "" {
config.LogoURL = coreOSLogoURL
}
if config.Issuer == "" {
config.Issuer = "dex"
}
return &templates{
globalData: config,
loginTmpl: tmpls.Lookup(tmplLogin),
approvalTmpl: tmpls.Lookup(tmplApproval),
passwordTmpl: tmpls.Lookup(tmplPassword),
}, nil
}
var scopeDescriptions = map[string]string{
"offline_access": "Have offline access",
"profile": "View basic profile information",
"email": "View your email",
}
type templates struct {
globalData TemplateConfig
loginTmpl *template.Template
approvalTmpl *template.Template
passwordTmpl *template.Template
}
type connectorInfo struct {
DisplayName string
URL string
ID string
Name string
URL string
}
var loginTmpl = template.Must(template.New("login-template").Parse(`<html>
<head></head>
<body>
<p>Login options</p>
{{ range $i, $connector := .Connectors }}
<a href="{{ $connector.URL }}?state={{ $.State }}">{{ $connector.DisplayName }}</a>
{{ end }}
</body>
</html>`))
func renderLoginOptions(w http.ResponseWriter, connectors []connectorInfo, state string) {
type byName []connectorInfo
func (n byName) Len() int { return len(n) }
func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (t *templates) login(w http.ResponseWriter, connectors []connectorInfo, state string) {
sort.Sort(byName(connectors))
data := struct {
TemplateConfig
Connectors []connectorInfo
State string
}{connectors, state}
renderTemplate(w, loginTmpl, data)
}{t.globalData, connectors, state}
renderTemplate(w, t.loginTmpl, data)
}
var passwordTmpl = template.Must(template.New("password-template").Parse(`<html>
<body>
<p>Login</p>
<form action="{{ .Callback }}" method="POST">
Login: <input type="text" name="login"/><br/>
Password: <input type="password" name="password"/><br/>
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="submit"/>
{{ if .Message }}
<p>Error: {{ .Message }}</p>
{{ end }}
</form>
</body>
</html>`))
func renderPasswordTmpl(w http.ResponseWriter, state, callback, message string) {
func (t *templates) password(w http.ResponseWriter, state, callback, lastUsername string, lastWasInvalid bool) {
data := struct {
TemplateConfig
State string
Callback string
Message string
}{state, callback, message}
renderTemplate(w, passwordTmpl, data)
PostURL string
Username string
Invalid bool
}{t.globalData, state, callback, lastUsername, lastWasInvalid}
renderTemplate(w, t.passwordTmpl, data)
}
var approvalTmpl = template.Must(template.New("approval-template").Parse(`<html>
<body>
<p>User: {{ .User }}</p>
<p>Client: {{ .ClientName }}</p>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="approve">
<button type="submit">Approve</button>
</form>
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="reject">
<button type="submit">Reject</button>
</form>
</body>
</html>`))
func renderApprovalTmpl(w http.ResponseWriter, state string, identity storage.Claims, client storage.Client, scopes []string) {
func (t *templates) approval(w http.ResponseWriter, state, username, clientName string, scopes []string) {
accesses := []string{}
for _, scope := range scopes {
access, ok := scopeDescriptions[scope]
if ok {
accesses = append(accesses, access)
}
}
sort.Strings(accesses)
data := struct {
User string
ClientName string
State string
}{identity.Email, client.Name, state}
renderTemplate(w, approvalTmpl, data)
TemplateConfig
User string
Client string
State string
Scopes []string
}{t.globalData, username, clientName, state, accesses}
renderTemplate(w, t.approvalTmpl, data)
}
func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
err := tmpl.Execute(w, data)
if err == nil {
return
}
// small io.Writer utilitiy to determine if executing the template wrote to the underlying response writer.
type writeRecorder struct {
wrote bool
w io.Writer
}
func (w *writeRecorder) Write(p []byte) (n int, err error) {
w.wrote = true
return w.w.Write(p)
}
switch err := err.(type) {
case template.ExecError:
// An ExecError guarentees that Execute has not written to the underlying reader.
func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interface{}) {
wr := &writeRecorder{w: w}
if err := tmpl.Execute(wr, data); err != nil {
log.Printf("Error rendering template %s: %s", tmpl.Name(), err)
// TODO(ericchiang): replace with better internal server error.
http.Error(w, "Internal server error", http.StatusInternalServerError)
default:
// An error with the underlying write, such as the connection being
// dropped. Ignore for now.
if !wr.wrote {
// TODO(ericchiang): replace with better internal server error.
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
return
}
// This file was generated by the makefile. Do not edit.
package server
// defaultTemplates is a key for file name to file data of the files in web/templates.
var defaultTemplates = map[string]string{
"approval.html": "{{ template \"header.html\" . }}\n\n<div class=\"panel\">\n <h2 class=\"heading\">Grant Access</h2>\n\n <hr>\n <div class=\"list-with-title\">\n <div class=\"subtle-text\">{{ .Client }} would like to:</div>\n {{ range $scope := .Scopes }}\n <li class=\"bullet-point\">\n <div class=\"subtle-text\">\n {{ $scope }}\n </div>\n </li>\n {{ end }}\n </div>\n <hr>\n\n <div>\n <div class=\"form-row\">\n <form method=\"post\">\n <input type=\"hidden\" name=\"state\" value=\"{{ .State }}\"/>\n <input type=\"hidden\" name=\"approval\" value=\"approve\">\n <button type=\"submit\" class=\"btn btn-success\">\n <span class=\"btn-text\">Grant Access</span>\n </button>\n </form>\n </div>\n <div class=\"form-row\">\n <form method=\"post\">\n <input type=\"hidden\" name=\"state\" value=\"{{ .State }}\"/>\n <input type=\"hidden\" name=\"approval\" value=\"rejected\">\n <button type=\"submit\" class=\"btn btn-provider\">\n <span class=\"btn-text\">Cancel</span>\n </button>\n </form>\n </div>\n </div>\n\n</div>\n\n{{ template \"footer.html\" . }}\n",
"footer.html": " </div>\n </body>\n</html>\n",
"header.html": "<!DOCTYPE html>\n<html>\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n <title>{{ .Issuer }}</title>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <style>\n * {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n }\n\n html,\n body {\n margin: 0;\n background-color: #efefef;\n font-family: 'Source Sans Pro', Helvetica, sans-serif;\n color: #333;\n }\n a {\n color: #428BCA;\n text-decoration: none;\n }\n a:active, a:hover, a:visited {\n color: #2A6596;\n text-decoration: underline;\n }\n #navbar {\n background-color: #fff;\n color: #333;\n height: 46px;\n box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);\n font-size: 13px;\n font-weight: 100;\n overflow: hidden;\n padding: 0 10px;\n }\n #navbar-logo-wrap {\n width: 300px;\n height: 100%;\n display: inline-block;\n overflow: hidden;\n padding: 10px 15px;\n }\n #navbar-logo {\n height: 100%;\n max-height: 25px;\n }\n #container {\n margin: 45px auto;\n text-align: center;\n max-width: 500px;\n min-width: 320px;\n }\n .heading {\n font-size: 20px;\n font-weight: 500;\n margin-top: 0;\n margin-bottom: 10px;\n }\n .footer {\n margin: 30px;\n }\n .input-label-right {\n position: absolute;\n right: 0;\n bottom: 0;\n }\n .input-desc {\n width: 250px;\n margin: 4px auto;\n text-align: left;\n position: relative;\n }\n .subtle-text {\n color: #999;\n font-size: 12px;\n }\n .panel {\n background-color: #fff;\n padding: 30px;\n box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.5);\n }\n .explain {\n font-size: 13px;\n color: #666;\n }\n\n .btn {\n box-shadow: inset 0 1px 0px rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.25), 0 0px 1px rgba(0, 0, 0, 0.25);\n padding: 0;\n font-size: 14px;\n border-radius: 4px;\n border: none;\n cursor: pointer;\n font-size: 16px;\n }\n .btn:focus {\n outline: none;\n }\n .btn:active {\n outline: none;\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n }\n .btn-primary {\n color: #fff;\n background-color: #333;\n padding: 6px 12px;\n min-width: 200px;\n border: none;\n }\n .btn-primary:hover {\n background-color: #666;\n color: #fff;\n }\n .btn-provider {\n background-color: #fff;\n color: #333;\n width: 250px;\n }\n .btn-provider:hover {\n color: #999;\n }\n .btn-success {\n background-color: #2FC98E;\n color: #fff;\n width: 250px;\n }\n .btn-success:hover {\n background-color: #49E3A8;\n }\n .btn-icon {\n width: 36px;\n height: 36px;\n float: left;\n margin-right: 5px;\n background-repeat: no-repeat;\n background-position: center;\n background-size: 24px;\n }\n .btn-icon-google {\n background-color: #DB4437;\n background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzdweCIgdmlld0JveD0iMCAwIDM2IDM3IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5TaGFwZSArIGcrPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSIzLjg0OTMxNTA3JSIgeTE9IjM0LjQ3MzI2MiUiIHgyPSI5Mi4yODU0Nzk1JSIgeTI9IjcwLjIyMzI2MiUiIGlkPSJsaW5lYXJHcmFkaWVudC0xIj4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzNFMjcyMyIgc3RvcC1vcGFjaXR5PSIwLjIiIG9mZnNldD0iMCUiPjwvc3RvcD4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzNFMjcyMyIgc3RvcC1vcGFjaXR5PSIwLjAyIiBvZmZzZXQ9IjEwMCUiPjwvc3RvcD4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9IkEuMS1WZXJpZnktRW1haWwtU2NyZWVuX2xvZ2luLSIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwNy4wMDAwMDAsIC0yNzIuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJTaGFwZS0rLWcrIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MDcuMDAwMDAwLCAyNzIuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMzUuOTYzOTg4MSwxNS4zMjM2OTA1IEwxOC43OTIzMjE0LDAuNzUyMDIzODEgTDEwLjc1Nzk3NjIsMC43NTIwMjM4MSBDNS4xNDQ5NDA0OCwwLjc1MjAyMzgxIDIuMzkzNDUyMzgsNC4xNjM4NjkwNSAyLjM5MzQ1MjM4LDguMDE1OTUyMzggQzIuMzkzNDUyMzgsMTAuOTg3NTU5NSA0LjgxNDc2MTksMTQuMjg5MzQ1MiA4Ljg4Njk2NDI5LDE0LjI4OTM0NTIgTDkuODc3NSwxNC4yODkzNDUyIEM5LjY1NzM4MDk1LDE0LjYxOTUyMzggOS41NDczMjE0MywxNS4yNzk4ODEgOS41NDczMjE0MywxNS43MjAxMTkgQzkuNTQ3MzIxNDMsMTYuODIwNzE0MyA5Ljk4NzU1OTUyLDE3LjM3MTAxMTkgMTAuNjQ3OTE2NywxOC4wMzEzNjkgQzguODg2OTY0MjksMTguMTQxNDI4NiA1LjU4NTE3ODU3LDE4LjQ3MTYwNzEgMy4xNjM4NjkwNSwyMC4wMTI0NDA1IEMwLjg1MjYxOTA0OCwyMS4zMzMxNTQ4IDAuMTkyMjYxOTA1LDIzLjMxNDIyNjIgMC4xOTIyNjE5MDUsMjQuNzQ1IEMwLjE5MjI2MTkwNSwyNS45NTU2NTQ4IDAuNjMyNSwyNy4wNTYyNSAxLjYyMzAzNTcxLDI4LjA0Njc4NTcgTDE1LjQ5MDUzNTcsMzYuOTE0Mjg1NyBMMzUuOTYzOTg4MSwzNi45MTQyODU3IEwzNS45NjM5ODgxLDE1LjMyMzY5MDUgWiIgaWQ9IlNoYXBlIiBmaWxsPSJ1cmwoI2xpbmVhckdyYWRpZW50LTEpIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZysiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMTkyMjYyLCAxLjE5MjI2MikiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xOC42MDAwNTk1LC0wLjExMDA1OTUyNCBMMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgTDEwLjU2NTcxNDMsLTAuMTEwMDU5NTI0IEM0Ljk1MjY3ODU3LC0wLjExMDA1OTUyNCAyLjIwMTE5MDQ4LDMuMzAxNzg1NzEgMi4yMDExOTA0OCw3LjE1Mzg2OTA1IEMyLjIwMTE5MDQ4LDEwLjEyNTQ3NjIgNC42MjI1LDEzLjQyNzI2MTkgOC42OTQ3MDIzOCwxMy40MjcyNjE5IEw5LjY4NTIzODEsMTMuNDI3MjYxOSBDOS40NjUxMTkwNSwxMy43NTc0NDA1IDkuMzU1MDU5NTIsMTQuNDE3Nzk3NiA5LjM1NTA1OTUyLDE0Ljk2ODA5NTIgQzkuMzU1MDU5NTIsMTYuMDY4NjkwNSA5Ljc5NTI5NzYyLDE2LjYxODk4ODEgMTAuNDU1NjU0OCwxNy4yNzkzNDUyIEM4LjY5NDcwMjM4LDE3LjM4OTQwNDggNS4zOTI5MTY2NywxNy43MTk1ODMzIDIuOTcxNjA3MTQsMTkuMjYwNDE2NyBDMC42NjAzNTcxNDMsMjAuNTgxMTMxIDAsMjIuNTYyMjAyNCAwLDIzLjk5Mjk3NjIgQzAsMjYuODU0NTIzOCAyLjc1MTQ4ODEsMjkuNDk1OTUyNCA4LjM2NDUyMzgxLDI5LjQ5NTk1MjQgQzE1LjA3ODE1NDgsMjkuNDk1OTUyNCAxOC42MDAwNTk1LDI1Ljg2Mzk4ODEgMTguNjAwMDU5NSwyMi4yMzIwMjM4IEMxOC42MDAwNTk1LDE5LjQ4MDUzNTcgMTcuMDU5MjI2MiwxOC4xNTk4MjE0IDE1LjI5ODI3MzgsMTYuNzI5MDQ3NiBMMTMuODY3NSwxNS42Mjg0NTI0IEMxMy40MjcyNjE5LDE1LjI5ODI3MzggMTIuOTg3MDIzOCwxNC44NTgwMzU3IDEyLjk4NzAyMzgsMTMuOTc3NTU5NSBDMTIuOTg3MDIzOCwxMy4wOTcwODMzIDEzLjUzNzMyMTQsMTIuNDM2NzI2MiAxNC4wODc2MTksMTIuMTA2NTQ3NiBDMTUuNzM4NTExOSwxMC43ODU4MzMzIDE3LjM4OTQwNDgsOS40NjUxMTkwNSAxNy4zODk0MDQ4LDYuNDkzNTExOSBDMTcuMzg5NDA0OCwzLjc0MjAyMzgxIDE1LjczODUxMTksMi4zMTEyNSAxNC43NDc5NzYyLDEuNTQwODMzMzMgTDE2Ljk0OTE2NjcsMS41NDA4MzMzMyBMMTguNjAwMDU5NSwtMC4xMTAwNTk1MjQgTDE4LjYwMDA1OTUsLTAuMTEwMDU5NTI0IFogTTE2LjA2ODY5MDUsMjMuNjYyNzk3NiBDMTYuMDY4NjkwNSwyNS44NjM5ODgxIDE0LjA4NzYxOSwyNy44NDUwNTk1IDEwLjM0NTU5NTIsMjcuODQ1MDU5NSBDNi4xNjMzMzMzMywyNy44NDUwNTk1IDMuNTIxOTA0NzYsMjUuNzUzOTI4NiAzLjUyMTkwNDc2LDIzLjExMjUgQzMuNTIxOTA0NzYsMjAuMzYxMDExOSA2LjA1MzI3MzgxLDE5LjM3MDQ3NjIgNi44MjM2OTA0OCwxOS4wNDAyOTc2IEM4LjQ3NDU4MzMzLDE4LjQ5IDEwLjU2NTcxNDMsMTguMzc5OTQwNSAxMC44OTU4OTI5LDE4LjM3OTk0MDUgTDExLjc3NjM2OSwxOC4zNzk5NDA1IEMxNC43NDc5NzYyLDIwLjU4MTEzMSAxNi4wNjg2OTA1LDIxLjY4MTcyNjIgMTYuMDY4NjkwNSwyMy42NjI3OTc2IEwxNi4wNjg2OTA1LDIzLjY2Mjc5NzYgWiBNMTAuNTY1NzE0MywxMi4xMDY1NDc2IEM3LjI2MzkyODU3LDEyLjEwNjU0NzYgNS41MDI5NzYxOSw4LjI1NDQ2NDI5IDUuNTAyOTc2MTksNS4yODI4NTcxNCBDNS41MDI5NzYxOSwyLjUzMTM2OTA1IDcuMjYzOTI4NTcsMS4yMTA2NTQ3NiA5LjEzNDk0MDQ4LDEuMjEwNjU0NzYgQzEyLjY1Njg0NTIsMS4yMTA2NTQ3NiAxNC40MTc3OTc2LDUuNjEzMDM1NzEgMTQuNDE3Nzk3Niw4LjI1NDQ2NDI5IEMxNC4zMDc3MzgxLDExLjQ0NjE5MDUgMTEuNTU2MjUsMTIuMTA2NTQ3NiAxMC41NjU3MTQzLDEyLjEwNjU0NzYgTDEwLjU2NTcxNDMsMTIuMTA2NTQ3NiBaIE0yNi40MTQyODU3LDEyLjk4NzAyMzggTDI2LjQxNDI4NTcsOC4wMzQzNDUyNCBMMjQuNzYzMzkyOSw4LjAzNDM0NTI0IEwyNC43NjMzOTI5LDEyLjk4NzAyMzggTDE5LjgxMDcxNDMsMTIuOTg3MDIzOCBMMTkuODEwNzE0MywxNC42Mzc5MTY3IEwyNC43NjMzOTI5LDE0LjYzNzkxNjcgTDI0Ljc2MzM5MjksMTkuNTkwNTk1MiBMMjYuNDE0Mjg1NywxOS41OTA1OTUyIEwyNi40MTQyODU3LDE0LjYzNzkxNjcgTDMxLjM2Njk2NDMsMTQuNjM3OTE2NyBMMzEuMzY2OTY0MywxMi45ODcwMjM4IEwyNi40MTQyODU3LDEyLjk4NzAyMzggTDI2LjQxNDI4NTcsMTIuOTg3MDIzOCBaIiBpZD0iU2hhcGUiIG9wYWNpdHk9IjAuMTYiIGZpbGw9IiMzRTI3MjMiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgTDEwLjU2NTcxNDMsLTAuNDQwMjM4MDk1IEM0Ljk1MjY3ODU3LC0wLjQ0MDIzODA5NSAyLjIwMTE5MDQ4LDIuOTcxNjA3MTQgMi4yMDExOTA0OCw2LjgyMzY5MDQ4IEMyLjIwMTE5MDQ4LDkuNzk1Mjk3NjIgNC42MjI1LDEzLjA5NzA4MzMgOC42OTQ3MDIzOCwxMy4wOTcwODMzIEw5LjY4NTIzODEsMTMuMDk3MDgzMyBDOS40NjUxMTkwNSwxMy40MjcyNjE5IDkuMzU1MDU5NTIsMTQuMDg3NjE5IDkuMzU1MDU5NTIsMTQuNTI3ODU3MSBDOS4zNTUwNTk1MiwxNS42Mjg0NTI0IDkuNzk1Mjk3NjIsMTYuMTc4NzUgMTAuNDU1NjU0OCwxNi44MzkxMDcxIEM4LjY5NDcwMjM4LDE2Ljk0OTE2NjcgNS4zOTI5MTY2NywxNy4yNzkzNDUyIDIuOTcxNjA3MTQsMTguODIwMTc4NiBDMC42NjAzNTcxNDMsMjAuMTQwODkyOSAwLDIyLjEyMTk2NDMgMCwyMy41NTI3MzgxIEMwLDI2LjQxNDI4NTcgMi43NTE0ODgxLDI5LjA1NTcxNDMgOC4zNjQ1MjM4MSwyOS4wNTU3MTQzIEMxNS4wNzgxNTQ4LDI5LjA1NTcxNDMgMTguNjAwMDU5NSwyNS40MjM3NSAxOC42MDAwNTk1LDIxLjc5MTc4NTcgQzE4LjYwMDA1OTUsMTkuMDQwMjk3NiAxNy4wNTkyMjYyLDE3LjcxOTU4MzMgMTUuMjk4MjczOCwxNi4yODg4MDk1IEwxMy44Njc1LDE1LjE4ODIxNDMgQzEzLjQyNzI2MTksMTQuODU4MDM1NyAxMi45ODcwMjM4LDE0LjQxNzc5NzYgMTIuOTg3MDIzOCwxMy41MzczMjE0IEMxMi45ODcwMjM4LDEyLjY1Njg0NTIgMTMuNTM3MzIxNCwxMS45OTY0ODgxIDE0LjA4NzYxOSwxMS42NjYzMDk1IEMxNS43Mzg1MTE5LDEwLjM0NTU5NTIgMTcuMzg5NDA0OCw5LjAyNDg4MDk1IDE3LjM4OTQwNDgsNi4wNTMyNzM4MSBDMTcuMzg5NDA0OCwzLjMwMTc4NTcxIDE1LjczODUxMTksMS44NzEwMTE5IDE0Ljc0Nzk3NjIsMS4xMDA1OTUyNCBMMTYuOTQ5MTY2NywxLjEwMDU5NTI0IEwxOC42MDAwNTk1LC0wLjQ0MDIzODA5NSBMMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgWiBNMTYuMDY4NjkwNSwyMy4zMzI2MTkgQzE2LjA2ODY5MDUsMjUuNTMzODA5NSAxNC4wODc2MTksMjcuNTE0ODgxIDEwLjM0NTU5NTIsMjcuNTE0ODgxIEM2LjE2MzMzMzMzLDI3LjUxNDg4MSAzLjUyMTkwNDc2LDI1LjQyMzc1IDMuNTIxOTA0NzYsMjIuNzgyMzIxNCBDMy41MjE5MDQ3NiwyMC4wMzA4MzMzIDYuMDUzMjczODEsMTkuMDQwMjk3NiA2LjgyMzY5MDQ4LDE4LjcxMDExOSBDOC40NzQ1ODMzMywxOC4xNTk4MjE0IDEwLjU2NTcxNDMsMTguMDQ5NzYxOSAxMC44OTU4OTI5LDE4LjA0OTc2MTkgTDExLjc3NjM2OSwxOC4wNDk3NjE5IEMxNC43NDc5NzYyLDIwLjI1MDk1MjQgMTYuMDY4NjkwNSwyMS4zNTE1NDc2IDE2LjA2ODY5MDUsMjMuMzMyNjE5IEwxNi4wNjg2OTA1LDIzLjMzMjYxOSBaIE0xMC41NjU3MTQzLDExLjg4NjQyODYgQzcuMjYzOTI4NTcsMTEuODg2NDI4NiA1LjUwMjk3NjE5LDguMDM0MzQ1MjQgNS41MDI5NzYxOSw1LjA2MjczODEgQzUuNTAyOTc2MTksMi4zMTEyNSA3LjI2MzkyODU3LDAuOTkwNTM1NzE0IDkuMTM0OTQwNDgsMC45OTA1MzU3MTQgQzEyLjY1Njg0NTIsMC45OTA1MzU3MTQgMTQuNDE3Nzk3Niw1LjM5MjkxNjY3IDE0LjQxNzc5NzYsOC4wMzQzNDUyNCBDMTQuMzA3NzM4MSwxMS4yMjYwNzE0IDExLjU1NjI1LDExLjg4NjQyODYgMTAuNTY1NzE0MywxMS44ODY0Mjg2IEwxMC41NjU3MTQzLDExLjg4NjQyODYgWiBNMjYuNDE0Mjg1NywxMi42NTY4NDUyIEwyNi40MTQyODU3LDcuNzA0MTY2NjcgTDI0Ljc2MzM5MjksNy43MDQxNjY2NyBMMjQuNzYzMzkyOSwxMi42NTY4NDUyIEwxOS44MTA3MTQzLDEyLjY1Njg0NTIgTDE5LjgxMDcxNDMsMTQuMzA3NzM4MSBMMjQuNzYzMzkyOSwxNC4zMDc3MzgxIEwyNC43NjMzOTI5LDE5LjI2MDQxNjcgTDI2LjQxNDI4NTcsMTkuMjYwNDE2NyBMMjYuNDE0Mjg1NywxNC4zMDc3MzgxIEwzMS4zNjY5NjQzLDE0LjMwNzczODEgTDMxLjM2Njk2NDMsMTIuNjU2ODQ1MiBMMjYuNDE0Mjg1NywxMi42NTY4NDUyIEwyNi40MTQyODU3LDEyLjY1Njg0NTIgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRjFGMUYxIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkuNzk1Mjk3NjIsMTMuMzE3MjAyNCBMOS43OTUyOTc2MiwxMy4wOTcwODMzIEM5LjU3NTE3ODU3LDEzLjQyNzI2MTkgOS40NjUxMTkwNSwxNC4wODc2MTkgOS40NjUxMTkwNSwxNC41Mjc4NTcxIEw5LjQ2NTExOTA1LDE0LjYzNzkxNjcgQzkuNDY1MTE5MDUsMTQuMTk3Njc4NiA5LjU3NTE3ODU3LDEzLjY0NzM4MSA5Ljc5NTI5NzYyLDEzLjMxNzIwMjQgTDkuNzk1Mjk3NjIsMTMuMzE3MjAyNCBaIE0xMC40NTU2NTQ4LDE2Ljk0OTE2NjcgQzguNjk0NzAyMzgsMTcuMDU5MjI2MiA1LjM5MjkxNjY3LDE3LjM4OTQwNDggMi45NzE2MDcxNCwxOC45MzAyMzgxIEMwLjY2MDM1NzE0MywyMC4yNTA5NTI0IDAsMjIuMjMyMDIzOCAwLDIzLjY2Mjc5NzYgTDAsMjMuNzcyODU3MSBDMC4xMTAwNTk1MjQsMjIuMzQyMDgzMyAwLjc3MDQxNjY2NywyMC40NzEwNzE0IDIuOTcxNjA3MTQsMTkuMTUwMzU3MSBDNS4zOTI5MTY2NywxNy43MTk1ODMzIDguNjk0NzAyMzgsMTcuMjc5MzQ1MiAxMC40NTU2NTQ4LDE3LjE2OTI4NTcgTDEwLjQ1NTY1NDgsMTYuOTQ5MTY2NyBMMTAuNDU1NjU0OCwxNi45NDkxNjY3IFogTTEwLjM0NTU5NTIsMjcuNTE0ODgxIEM2LjI3MzM5Mjg2LDI3LjUxNDg4MSAzLjYzMTk2NDI5LDI1LjUzMzgwOTUgMy41MjE5MDQ3NiwyMi44OTIzODEgTDMuNTIxOTA0NzYsMjMuMDAyNDQwNSBDMy41MjE5MDQ3NiwyNS42NDM4NjkgNi4xNjMzMzMzMywyNy43MzUgMTAuMzQ1NTk1MiwyNy43MzUgQzE0LjA4NzYxOSwyNy43MzUgMTYuMDY4NjkwNSwyNS43NTM5Mjg2IDE2LjA2ODY5MDUsMjMuNTUyNzM4MSBMMTYuMDY4NjkwNSwyMy40NDI2Nzg2IEMxNS45NTg2MzEsMjUuNjQzODY5IDEzLjk3NzU1OTUsMjcuNTE0ODgxIDEwLjM0NTU5NTIsMjcuNTE0ODgxIEwxMC4zNDU1OTUyLDI3LjUxNDg4MSBaIE0xNC4zMDc3MzgxLDguMjU0NDY0MjkgTDE0LjMwNzczODEsOC4xNDQ0MDQ3NiBDMTQuMTk3Njc4NiwxMS4zMzYxMzEgMTEuNTU2MjUsMTEuODg2NDI4NiAxMC40NTU2NTQ4LDExLjg4NjQyODYgQzcuMjYzOTI4NTcsMTEuODg2NDI4NiA1LjM5MjkxNjY3LDguMTQ0NDA0NzYgNS4zOTI5MTY2Nyw1LjE3Mjc5NzYyIEw1LjM5MjkxNjY3LDUuMjgyODU3MTQgQzUuMzkyOTE2NjcsOC4yNTQ0NjQyOSA3LjE1Mzg2OTA1LDEyLjEwNjU0NzYgMTAuNDU1NjU0OCwxMi4xMDY1NDc2IEMxMS41NTYyNSwxMi4xMDY1NDc2IDE0LjMwNzczODEsMTEuNDQ2MTkwNSAxNC4zMDc3MzgxLDguMjU0NDY0MjkgTDE0LjMwNzczODEsOC4yNTQ0NjQyOSBaIE0xNS40MDgzMzMzLDE2LjI4ODgwOTUgTDEzLjk3NzU1OTUsMTUuMTg4MjE0MyBDMTMuNTM3MzIxNCwxNC44NTgwMzU3IDEzLjIwNzE0MjksMTQuNDE3Nzk3NiAxMy4wOTcwODMzLDEzLjY0NzM4MSBMMTMuMDk3MDgzMywxMy43NTc0NDA1IEMxMy4wOTcwODMzLDE0LjYzNzkxNjcgMTMuNTM3MzIxNCwxNS4wNzgxNTQ4IDEzLjk3NzU1OTUsMTUuNDA4MzMzMyBMMTUuNDA4MzMzMywxNi41MDg5Mjg2IEMxNy4wNTkyMjYyLDE3LjkzOTcwMjQgMTguNjAwMDU5NSwxOS4yNjA0MTY3IDE4LjcxMDExOSwyMS43OTE3ODU3IEwxOC43MTAxMTksMjEuNjgxNzI2MiBDMTguNjAwMDU5NSwxOS4wNDAyOTc2IDE3LjA1OTIyNjIsMTcuODI5NjQyOSAxNS40MDgzMzMzLDE2LjI4ODgwOTUgTDE1LjQwODMzMzMsMTYuMjg4ODA5NSBaIE0yNi40MTQyODU3LDEyLjY1Njg0NTIgTDI2LjQxNDI4NTcsMTIuODc2OTY0MyBMMzEuMzY2OTY0MywxMi44NzY5NjQzIEwzMS4zNjY5NjQzLDEyLjY1Njg0NTIgTDI2LjQxNDI4NTcsMTIuNjU2ODQ1MiBMMjYuNDE0Mjg1NywxMi42NTY4NDUyIFogTTE3LjQ5OTQ2NDMsNi4yNzMzOTI4NiBMMTcuNDk5NDY0Myw2LjE2MzMzMzMzIEMxNy40OTk0NjQzLDMuNDExODQ1MjQgMTUuODQ4NTcxNCwxLjk4MTA3MTQzIDE0Ljg1ODAzNTcsMS4yMTA2NTQ3NiBMMTQuODU4MDM1NywxLjU0MDgzMzMzIEMxNS44NDg1NzE0LDIuMjAxMTkwNDggMTcuNDk5NDY0MywzLjYzMTk2NDI5IDE3LjQ5OTQ2NDMsNi4yNzMzOTI4NiBMMTcuNDk5NDY0Myw2LjI3MzM5Mjg2IFogTTI2LjQxNDI4NTcsNy43MDQxNjY2NyBMMjQuNzYzMzkyOSw3LjcwNDE2NjY3IEwyNC43NjMzOTI5LDcuOTI0Mjg1NzEgTDI2LjQxNDI4NTcsNy45MjQyODU3MSBMMjYuNDE0Mjg1Nyw3LjcwNDE2NjY3IEwyNi40MTQyODU3LDcuNzA0MTY2NjcgWiBNMi4zMTEyNSw2LjgyMzY5MDQ4IEwyLjMxMTI1LDYuOTMzNzUgQzIuNDIxMzA5NTIsMy4wODE2NjY2NyA1LjA2MjczODEsLTAuMTEwMDU5NTI0IDEwLjU2NTcxNDMsLTAuMTEwMDU5NTI0IEwxOC4zNzk5NDA1LC0wLjExMDA1OTUyNCBMMTguNzEwMTE5LC0wLjQ0MDIzODA5NSBMMTAuNjc1NzczOCwtMC40NDAyMzgwOTUgQzQuOTUyNjc4NTcsLTAuNDQwMjM4MDk1IDIuMzExMjUsMi45NzE2MDcxNCAyLjMxMTI1LDYuODIzNjkwNDggTDIuMzExMjUsNi44MjM2OTA0OCBaIE0xOS44MTA3MTQzLDEyLjk4NzAyMzggTDI0Ljc2MzM5MjksMTIuOTg3MDIzOCBMMjQuNzYzMzkyOSwxMi43NjY5MDQ4IEwxOS44MTA3MTQzLDEyLjc2NjkwNDggTDE5LjgxMDcxNDMsMTIuOTg3MDIzOCBMMTkuODEwNzE0MywxMi45ODcwMjM4IFoiIGlkPSJTaGFwZSIgZmlsbC1vcGFjaXR5PSIwLjY0IiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);\n }\n .btn-icon-local {\n background-color: #84B6EF;\n background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjBweCIgdmlld0JveD0iMCAwIDI0IDIwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5SZWN0YW5nbGUgMjkxICsgUGF0aCAyMzI8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8ZyBpZD0iQS4xLVZlcmlmeS1FbWFpbC1TY3JlZW5fbG9naW4tIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDA5LjAwMDAwMCwgLTIwOS4wMDAwMDApIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0iI0ZGRkZGRiI+CiAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMzktQ29weS02LSstRW1haWwtQ29weS0rLVJlY3RhbmdsZS0yOTAtKy1SZWN0YW5nbGUtMjkxLSstUGF0aC0yMzIiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQwMC4wMDAwMDAsIDE5Ny4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMjkwLSstUmVjdGFuZ2xlLTI5MS0rLVBhdGgtMjMyIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iUmVjdGFuZ2xlLTI5MS0rLVBhdGgtMjMyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5LjAwMDAwMCwgMTIuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0yOTEiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNCIgaGVpZ2h0PSIxOS4zNSI+PC9yZWN0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsMS45MjcyNzEzOSBMMTEuNjExMzAxOSwxMi45IEwyNCwxLjE5MjYyODgxIiBpZD0iUGF0aC0yMzIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);\n }\n .btn-icon-coreos {\n /* B&W CoreOS SVG logo */\n background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSIjNjY2IiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDIxNSAyMTUiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KICA8Zz4NCiAgICA8Zz4NCiAgICAgIDxwYXRoIGQ9Ik0xMDcuNDc5LDEuMDc1Yy01OC42NzcsMC0xMDYuNDA0LDQ3LjczLTEwNi40MDQsMTA2LjM5OGMwLDU4LjY3Miw0Ny43MjcsMTA2LjM5OSwxMDYuNDA0LDEwNi4zOTkNCiAgICAgICAgICAgICAgYzU4LjY1OSwwLDEwNi4zOS00Ny43MjcsMTA2LjM5LTEwNi4zOTlDMjEzLjg2OSw0OC44MDUsMTY2LjEzOCwxLjA3NSwxMDcuNDc5LDEuMDc1eiBNMTQ3LjQ0OSwxMzQuNjI3DQogICAgICAgICAgICAgIGMtMC44OCwwLjEyOC0xLjc0OSwwLjI1MS0yLjYzMiwwLjM2NGMtOC4wMywxLjAzOC0xNi42MDIsMS43NDMtMjUuNTYxLDIuMDc4Yy0zLjg1NiwwLjE0NC03Ljc5MywwLjIzMS0xMS43NzYsMC4yMzENCiAgICAgICAgICAgICAgYy0zLjk5NSwwLTcuOTItMC4wODYtMTEuNzg4LTAuMjMxYy04Ljk0OC0wLjMzNS0xNy41MjYtMS4wNC0yNS41NDktMi4wNzhjLTAuNzE2LTUuOTg3LTEuMjAxLTEyLjIxNi0xLjQ0My0xOC42MjMNCiAgICAgICAgICAgICAgYy0wLjExNi0yLjkyNi0wLjE3My01Ljg5Ni0wLjE3My04Ljg5NWMwLTMuMDAyLDAuMDU3LTUuOTY2LDAuMTczLTguODk4YzAuMjQzLTYuNDA4LDAuNzI4LTEyLjYzMywxLjQ0My0xOC42Mg0KICAgICAgICAgICAgICBjMC4xNDQtMS4yNDYsMC4zMDYtMi40ODUsMC40NzMtMy43MDljNS4yNDEtMzguMDQsMTkuNzUyLTY1LjQwOCwzNi44NjMtNjUuNDA4YzUzLjM2NCwwLDk2LjYzMiw0My4yNjIsOTYuNjMzLDk2LjYzNQ0KICAgICAgICAgICAgICBDMjA0LjExMiwxMTkuNTQ3LDE4MC44NjYsMTI5LjkzNCwxNDcuNDQ5LDEzNC42Mjd6Ii8+DQogICAgICA8cGF0aCBkPSJNMTQ3LjQ0OCw4MC4zMTZjLTAuOTY5LTEuNDE0LTIuMDA5LTIuNzY4LTMuMTE3LTQuMDY5Yy04Ljg2Ni0xMC40NTEtMjIuMDc0LTE3LjA5Mi0zNi44NTItMTcuMDkyDQogICAgICAgICAgICAgIGMtNC43OTEsMC05LjA1Nyw3LjMzMy0xMS43ODgsMTguNzJjLTEuMDg1LDQuNTQtMS45MjgsOS43MjEtMi40NywxNS4zNDNjLTAuNDI4LDQuNTA1LTAuNjU4LDkuMjk3LTAuNjU4LDE0LjI1NQ0KICAgICAgICAgICAgICBzMC4yMzEsOS43NTEsMC42NTgsMTQuMjUyYzQuNTA4LDAuNDI4LDkuMjkzLDAuNjU3LDE0LjI1OCwwLjY1N2M0Ljk1OSwwLDkuNzQ0LTAuMjMsMTQuMjUyLTAuNjU3DQogICAgICAgICAgICAgIGM5LjkxMS0wLjk0LDE4LjQ2Ni0yLjg0NiwyNC41MjctNS4zNTdjNS45ODYtMi40NzYsOS41MjgtNS41NTksOS41MjgtOC44OTVDMTU1Ljc4Niw5Ny40MDcsMTUyLjcxMiw4OC4wNTcsMTQ3LjQ0OCw4MC4zMTZ6Ii8+DQogICAgPC9nPg0KICA8L2c+DQo8L3N2Zz4NCg==);\n }\n .btn-icon-github {\n background-color: #F5F5F5;\n background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTggMEMzLjU4IDAgMCAzLjU4IDAgOGMwIDMuNTQgMi4yOSA2LjUzIDUuNDcgNy41OSAwLjQgMC4wNyAwLjU1LTAuMTcgMC41NS0wLjM4IDAtMC4xOS0wLjAxLTAuODItMC4wMS0xLjQ5LTIuMDEgMC4zNy0yLjUzLTAuNDktMi42OS0wLjk0LTAuMDktMC4yMy0wLjQ4LTAuOTQtMC44Mi0xLjEzLTAuMjgtMC4xNS0wLjY4LTAuNTItMC4wMS0wLjUzIDAuNjMtMC4wMSAxLjA4IDAuNTggMS4yMyAwLjgyIDAuNzIgMS4yMSAxLjg3IDAuODcgMi4zMyAwLjY2IDAuMDctMC41MiAwLjI4LTAuODcgMC41MS0xLjA3LTEuNzgtMC4yLTMuNjQtMC44OS0zLjY0LTMuOTUgMC0wLjg3IDAuMzEtMS41OSAwLjgyLTIuMTUtMC4wOC0wLjItMC4zNi0xLjAyIDAuMDgtMi4xMiAwIDAgMC42Ny0wLjIxIDIuMiAwLjgyIDAuNjQtMC4xOCAxLjMyLTAuMjcgMi0wLjI3IDAuNjggMCAxLjM2IDAuMDkgMiAwLjI3IDEuNTMtMS4wNCAyLjItMC44MiAyLjItMC44MiAwLjQ0IDEuMSAwLjE2IDEuOTIgMC4wOCAyLjEyIDAuNTEgMC41NiAwLjgyIDEuMjcgMC44MiAyLjE1IDAgMy4wNy0xLjg3IDMuNzUtMy42NSAzLjk1IDAuMjkgMC4yNSAwLjU0IDAuNzMgMC41NCAxLjQ4IDAgMS4wNy0wLjAxIDEuOTMtMC4wMSAyLjIgMCAwLjIxIDAuMTUgMC40NiAwLjU1IDAuMzhDMTMuNzEgMTQuNTMgMTYgMTEuNTMgMTYgOCAxNiAzLjU4IDEyLjQyIDAgOCAweiIgLz4KPC9zdmc+Cg==);\n }\n .btn-icon-bitbucket {\n background-color: #205081;\n background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwMCIgd2lkdGg9Ijc4NS43MTQiPjxwYXRoIGQ9Ik00NTQuNzcgNDc5LjM4NnE0LjQ2NCAzNS4xNTQgLTI4LjE3OSA1Ni4zNTh0LTYyLjIxNyAzLjM0OHEtMjEuNzYyIC05LjQ4NiAtMjkuODUzIC0zMi4zNjR0LS4yNzkgLTQ1Ljc1NiAyOS4wMTYgLTMyLjM2NHEyMC4wODggLTEwLjA0NCA0MC40NTUgLTYuNjk2dDM1LjcxMiAxOS44MDkgMTUuMzQ1IDM3LjY2NXptNjEuOTM4IC0xMS43MThxLTcuODEyIC01OS43MDYgLTYzLjA1NCAtOTEuNTEydC0xMDkuOTI2IC03LjI1NHEtMzUuMTU0IDE1LjYyNCAtNTYuMDc5IDQ5LjM4M3QtMTkuMjUxIDcyLjI2MXEyLjIzMiA1MC43NzggNDMuMjQ1IDg2LjQ5dDkyLjM0OSAzMS4yNDhxNTAuNzc4IC00LjQ2NCA4NC44MTYgLTQ2Ljg3MnQyNy45IC05My43NDR6bTEzMy4zNjIgLTMwMi40MzZxLTExLjE2IC0xNS4wNjYgLTMxLjI0OCAtMjQuODMxdC0zMi4zNjQgLTEyLjI3NiAtMzkuNjE4IC02Ljk3NXEtMTYyLjM3OCAtMjYuMjI2IC0zMTUuODI4IDEuMTE2IC0yMy45OTQgMy45MDYgLTM2LjgyOCA2LjY5NnQtMzAuNjkgMTIuMjc2IC0yNy45IDIzLjk5NHExNi43NCAxNS42MjQgNDIuNDA4IDI1LjM4OXQ0MS4wMTMgMTIuMjc2IDQ4LjgyNSA2LjQxN3ExMjcuMjI0IDE2LjE4MiAyNDkuOTg0IC41NTggMzUuMTU0IC00LjQ2NCA0OS45NDEgLTYuNjk2dDQwLjQ1NSAtMTEuOTk3IDQxLjg1IC0yNS45NDd6bTMxLjgwNiA1NzcuNTNxLTQuNDY0IDE0LjUwOCAtOC42NDkgNDIuNjg3dC03LjgxMiA0Ni44NzIgLTE1LjkwMyAzOS4wNiAtMzIuMzY0IDMxLjUyN3EtNDcuOTg4IDI2Ljc4NCAtMTA1Ljc0MSAzOS44OTd0LTExMi43MTYgMTIuMjc2IC0xMTIuNDM3IC0xMC4zMjNxLTI1LjY2OCAtNC40NjQgLTQ1LjQ3NyAtMTAuMDQ0dC00Mi42ODcgLTE1LjA2NiAtNDAuNzM0IC0yNC4yNzMgLTI5LjAxNiAtMzQuMzE3cS0xMy45NSAtNTMuNTY4IC0zMS44MDYgLTE2Mi45MzZsMy4zNDggLTguOTI4IDEwLjA0NCAtNS4wMjJxMTI0LjQzNCA4Mi41ODQgMjgyLjYyNyA4Mi41ODR0MjgzLjE4NSAtODIuNTg0cTExLjcxOCAzLjM0OCAxMy4zOTIgMTIuODM0dC0yLjc5IDI1LjExIC00LjQ2NCAyMC42NDZ6bTEwMC45OTggLTUzNi4yMzhxLTE0LjUwOCA5My4xODYgLTYxLjkzOCAzNjUuNDkgLTIuNzkgMTYuNzQgLTE1LjA2NiAzMS4yNDh0LTI0LjI3MyAyMi4zMiAtMzAuNDExIDE3LjI5OHEtMTQwLjYxNiA3MC4zMDggLTM0MC4zOCA0OS4xMDQgLTEzOC4zODQgLTE1LjA2NiAtMjE5Ljg1MiAtNzcuNTYyIC04LjM3IC02LjY5NiAtMTQuMjI5IC0xNC43ODd0LTkuNDg2IC0xOS41MyAtNS4wMjIgLTE4Ljk3MiAtMy4zNDggLTIyLjA0MSAtMy4wNjkgLTE5LjUzcS01LjAyMiAtMjcuOSAtMTQuNzg3IC04My43dC0xNS42MjQgLTkwLjExNyAtMTMuMTEzIC04Mi4zMDUgLTEyLjI3NiAtODguMTY0cTEuNjc0IC0xNC41MDggOS43NjUgLTI3LjA2M3QxNy41NzcgLTIwLjkyNSAyNS4xMSAtMTYuNzQgMjUuNjY4IC0xMi41NTUgMjYuNzg0IC0xMC4zMjNxNjkuNzUgLTI1LjY2OCAxNzQuNjU0IC0zNS43MTIgMjExLjQ4MiAtMjAuNjQ2IDM3Ny4yMDggMjcuOSA4Ni40OSAyNS42NjggMTE5Ljk3IDY4LjA3NiA4LjkyOCAxMS4xNiA5LjIwNyAyOC40NTh0LTMuMDY5IDMwLjEzMnoiIGZpbGw9IiNGRkZGRkYiLz48L3N2Zz4K);\n }\n .btn-text {\n line-height: 36px;\n padding: 6px 12px;\n text-align: center;\n font-weight: 600;\n }\n .form-row {\n display: block;\n margin: 20px auto;\n }\n label {\n font-size: 13px;\n font-weight: 600;\n }\n .input-box {\n display: block;\n height: 36px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #666;\n border: 1px solid #CCC;\n border-radius: 4px;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n width: 250px;\n margin: auto;\n }\n .input-box:focus,\n .input-box:active {\n outline: none;\n border-color: #66AFE9;\n }\n .error-box-field,\n .error-box {\n background-color: #DD1327;\n max-width: 320px;\n color: #fff;\n font-size: 14px;\n font-weight: normal;\n padding: 4px 0;\n }\n .error-box {\n margin: 20px auto;\n }\n .error-box-field {\n margin: 0 auto;\n width: 250px;\n }\n .instruction-block {\n font-size: 14px;\n }\n .detail-block {\n color: #777;\n font-size: 12px;\n margin-top: 20px;\n }\n .bullet-point {\n list-style: square;\n }\n .list-with-title {\n text-align: left;\n margin: 0 25%;\n }\n .hr {\n color: #999;\n }\n </style>\n </head>\n\n <body>\n <div id=\"navbar\">\n <div id=\"navbar-logo-wrap\">\n <img id=\"navbar-logo\" src=\"{{ .LogoURL }}\">\n </div>\n </div>\n\n <div id=\"container\">\n\n",
"login.html": "{{ template \"header.html\" . }}\n\n<div class=\"panel\">\n <h2 class=\"heading\">Log in to {{ .Issuer }} </h2>\n\n <div>\n {{ range $c := .Connectors }}\n <div class=\"form-row\">\n <a href=\"{{ $c.URL }}?state={{ $.State }}\" target=\"_self\">\n <button class=\"btn btn-provider\">\n <span class=\"btn-icon btn-icon-{{ $c.ID }}\"></span>\n <span class=\"btn-text\">Log in with {{ $c.Name }}</span>\n </button>\n </a>\n </div>\n {{ end }}\n </div>\n\n</div>\n\n\n{{ template \"footer.html\" . }}\n",
"password.html": "{{ template \"header.html\" . }}\n\n<div class=\"panel\">\n <h2 class=\"heading\">Log in to Your Account</h2>\n <form method=\"post\" action=\"{{ .PostURL }}\">\n <div class=\"form-row\">\n <div class=\"input-desc\">\n <label for=\"userid\">Username</label>\n </div>\n\t <input tabindex=\"1\" required id=\"login\" name=\"login\" type=\"text\" class=\"input-box\" placeholder=\"username\" {{ if .Username }}value=\"{{ .Username }}\" {{ else }} autofocus {{ end }}/>\n </div>\n <div class=\"form-row\">\n <div class=\"input-desc\">\n <label for=\"password\">Password</label>\n </div>\n\t <input tabindex=\"2\" required id=\"password\" name=\"password\" type=\"password\" class=\"input-box\" placeholder=\"password\" {{ if .Invalid }} autofocus {{ end }}/>\n </div>\n <input type=\"hidden\" name=\"state\" value=\"{{ .State }}\"/>\n\n {{ if .Invalid }}\n <div class=\"error-box\">\n Invalid username and password.\n </div>\n {{ end }}\n\n <button tabindex=\"3\" type=\"submit\" class=\"btn btn-primary\">Login</button>\n\n </form>\n</div>\n\n{{ template \"footer.html\" . }}\n",
}
// +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
}
type fileData struct {
name string
data string
}
func main() {
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)
}
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: %q,\n", file.name, file.data)
}
fmt.Fprintln(f, "}")
if err := ioutil.WriteFile("server/templates_default.go", f.Bytes(), 0644); err != nil {
log.Fatal(err)
}
}
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"
}
{{ template "header.html" . }}
<div class="panel">
<h2 class="heading">Grant Access</h2>
<hr>
<div class="list-with-title">
<div class="subtle-text">{{ .Client }} would like to:</div>
{{ range $scope := .Scopes }}
<li class="bullet-point">
<div class="subtle-text">
{{ $scope }}
</div>
</li>
{{ end }}
</div>
<hr>
<div>
<div class="form-row">
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="approve">
<button type="submit" class="btn btn-success">
<span class="btn-text">Grant Access</span>
</button>
</form>
</div>
<div class="form-row">
<form method="post">
<input type="hidden" name="state" value="{{ .State }}"/>
<input type="hidden" name="approval" value="rejected">
<button type="submit" class="btn btn-provider">
<span class="btn-text">Cancel</span>
</button>
</form>
</div>
</div>
</div>
{{ template "footer.html" . }}
</div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>{{ .Issuer }}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
margin: 0;
background-color: #efefef;
font-family: 'Source Sans Pro', Helvetica, sans-serif;
color: #333;
}
a {
color: #428BCA;
text-decoration: none;
}
a:active, a:hover, a:visited {
color: #2A6596;
text-decoration: underline;
}
#navbar {
background-color: #fff;
color: #333;
height: 46px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
font-size: 13px;
font-weight: 100;
overflow: hidden;
padding: 0 10px;
}
#navbar-logo-wrap {
width: 300px;
height: 100%;
display: inline-block;
overflow: hidden;
padding: 10px 15px;
}
#navbar-logo {
height: 100%;
max-height: 25px;
}
#container {
margin: 45px auto;
text-align: center;
max-width: 500px;
min-width: 320px;
}
.heading {
font-size: 20px;
font-weight: 500;
margin-top: 0;
margin-bottom: 10px;
}
.footer {
margin: 30px;
}
.input-label-right {
position: absolute;
right: 0;
bottom: 0;
}
.input-desc {
width: 250px;
margin: 4px auto;
text-align: left;
position: relative;
}
.subtle-text {
color: #999;
font-size: 12px;
}
.panel {
background-color: #fff;
padding: 30px;
box-shadow: 0px 5px 15px rgba(0, 0, 0, 0.5);
}
.explain {
font-size: 13px;
color: #666;
}
.btn {
box-shadow: inset 0 1px 0px rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.25), 0 0px 1px rgba(0, 0, 0, 0.25);
padding: 0;
font-size: 14px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 16px;
}
.btn:focus {
outline: none;
}
.btn:active {
outline: none;
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
.btn-primary {
color: #fff;
background-color: #333;
padding: 6px 12px;
min-width: 200px;
border: none;
}
.btn-primary:hover {
background-color: #666;
color: #fff;
}
.btn-provider {
background-color: #fff;
color: #333;
width: 250px;
}
.btn-provider:hover {
color: #999;
}
.btn-success {
background-color: #2FC98E;
color: #fff;
width: 250px;
}
.btn-success:hover {
background-color: #49E3A8;
}
.btn-icon {
width: 36px;
height: 36px;
float: left;
margin-right: 5px;
background-repeat: no-repeat;
background-position: center;
background-size: 24px;
}
.btn-icon-google {
background-color: #DB4437;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjM2cHgiIGhlaWdodD0iMzdweCIgdmlld0JveD0iMCAwIDM2IDM3IiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5TaGFwZSArIGcrPC90aXRsZT4KICAgIDxkZXNjPkNyZWF0ZWQgd2l0aCBTa2V0Y2guPC9kZXNjPgogICAgPGRlZnM+CiAgICAgICAgPGxpbmVhckdyYWRpZW50IHgxPSIzLjg0OTMxNTA3JSIgeTE9IjM0LjQ3MzI2MiUiIHgyPSI5Mi4yODU0Nzk1JSIgeTI9IjcwLjIyMzI2MiUiIGlkPSJsaW5lYXJHcmFkaWVudC0xIj4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzNFMjcyMyIgc3RvcC1vcGFjaXR5PSIwLjIiIG9mZnNldD0iMCUiPjwvc3RvcD4KICAgICAgICAgICAgPHN0b3Agc3RvcC1jb2xvcj0iIzNFMjcyMyIgc3RvcC1vcGFjaXR5PSIwLjAyIiBvZmZzZXQ9IjEwMCUiPjwvc3RvcD4KICAgICAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPC9kZWZzPgogICAgPGcgaWQ9IlBhZ2UtMSIgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCIgc2tldGNoOnR5cGU9Ik1TUGFnZSI+CiAgICAgICAgPGcgaWQ9IkEuMS1WZXJpZnktRW1haWwtU2NyZWVuX2xvZ2luLSIgc2tldGNoOnR5cGU9Ik1TQXJ0Ym9hcmRHcm91cCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTQwNy4wMDAwMDAsIC0yNzIuMDAwMDAwKSI+CiAgICAgICAgICAgIDxnIGlkPSJTaGFwZS0rLWcrIiBza2V0Y2g6dHlwZT0iTVNMYXllckdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MDcuMDAwMDAwLCAyNzIuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMzUuOTYzOTg4MSwxNS4zMjM2OTA1IEwxOC43OTIzMjE0LDAuNzUyMDIzODEgTDEwLjc1Nzk3NjIsMC43NTIwMjM4MSBDNS4xNDQ5NDA0OCwwLjc1MjAyMzgxIDIuMzkzNDUyMzgsNC4xNjM4NjkwNSAyLjM5MzQ1MjM4LDguMDE1OTUyMzggQzIuMzkzNDUyMzgsMTAuOTg3NTU5NSA0LjgxNDc2MTksMTQuMjg5MzQ1MiA4Ljg4Njk2NDI5LDE0LjI4OTM0NTIgTDkuODc3NSwxNC4yODkzNDUyIEM5LjY1NzM4MDk1LDE0LjYxOTUyMzggOS41NDczMjE0MywxNS4yNzk4ODEgOS41NDczMjE0MywxNS43MjAxMTkgQzkuNTQ3MzIxNDMsMTYuODIwNzE0MyA5Ljk4NzU1OTUyLDE3LjM3MTAxMTkgMTAuNjQ3OTE2NywxOC4wMzEzNjkgQzguODg2OTY0MjksMTguMTQxNDI4NiA1LjU4NTE3ODU3LDE4LjQ3MTYwNzEgMy4xNjM4NjkwNSwyMC4wMTI0NDA1IEMwLjg1MjYxOTA0OCwyMS4zMzMxNTQ4IDAuMTkyMjYxOTA1LDIzLjMxNDIyNjIgMC4xOTIyNjE5MDUsMjQuNzQ1IEMwLjE5MjI2MTkwNSwyNS45NTU2NTQ4IDAuNjMyNSwyNy4wNTYyNSAxLjYyMzAzNTcxLDI4LjA0Njc4NTcgTDE1LjQ5MDUzNTcsMzYuOTE0Mjg1NyBMMzUuOTYzOTg4MSwzNi45MTQyODU3IEwzNS45NjM5ODgxLDE1LjMyMzY5MDUgWiIgaWQ9IlNoYXBlIiBmaWxsPSJ1cmwoI2xpbmVhckdyYWRpZW50LTEpIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8ZyBpZD0iZysiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuMTkyMjYyLCAxLjE5MjI2MikiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPgogICAgICAgICAgICAgICAgICAgIDxwYXRoIGQ9Ik0xOC42MDAwNTk1LC0wLjExMDA1OTUyNCBMMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgTDEwLjU2NTcxNDMsLTAuMTEwMDU5NTI0IEM0Ljk1MjY3ODU3LC0wLjExMDA1OTUyNCAyLjIwMTE5MDQ4LDMuMzAxNzg1NzEgMi4yMDExOTA0OCw3LjE1Mzg2OTA1IEMyLjIwMTE5MDQ4LDEwLjEyNTQ3NjIgNC42MjI1LDEzLjQyNzI2MTkgOC42OTQ3MDIzOCwxMy40MjcyNjE5IEw5LjY4NTIzODEsMTMuNDI3MjYxOSBDOS40NjUxMTkwNSwxMy43NTc0NDA1IDkuMzU1MDU5NTIsMTQuNDE3Nzk3NiA5LjM1NTA1OTUyLDE0Ljk2ODA5NTIgQzkuMzU1MDU5NTIsMTYuMDY4NjkwNSA5Ljc5NTI5NzYyLDE2LjYxODk4ODEgMTAuNDU1NjU0OCwxNy4yNzkzNDUyIEM4LjY5NDcwMjM4LDE3LjM4OTQwNDggNS4zOTI5MTY2NywxNy43MTk1ODMzIDIuOTcxNjA3MTQsMTkuMjYwNDE2NyBDMC42NjAzNTcxNDMsMjAuNTgxMTMxIDAsMjIuNTYyMjAyNCAwLDIzLjk5Mjk3NjIgQzAsMjYuODU0NTIzOCAyLjc1MTQ4ODEsMjkuNDk1OTUyNCA4LjM2NDUyMzgxLDI5LjQ5NTk1MjQgQzE1LjA3ODE1NDgsMjkuNDk1OTUyNCAxOC42MDAwNTk1LDI1Ljg2Mzk4ODEgMTguNjAwMDU5NSwyMi4yMzIwMjM4IEMxOC42MDAwNTk1LDE5LjQ4MDUzNTcgMTcuMDU5MjI2MiwxOC4xNTk4MjE0IDE1LjI5ODI3MzgsMTYuNzI5MDQ3NiBMMTMuODY3NSwxNS42Mjg0NTI0IEMxMy40MjcyNjE5LDE1LjI5ODI3MzggMTIuOTg3MDIzOCwxNC44NTgwMzU3IDEyLjk4NzAyMzgsMTMuOTc3NTU5NSBDMTIuOTg3MDIzOCwxMy4wOTcwODMzIDEzLjUzNzMyMTQsMTIuNDM2NzI2MiAxNC4wODc2MTksMTIuMTA2NTQ3NiBDMTUuNzM4NTExOSwxMC43ODU4MzMzIDE3LjM4OTQwNDgsOS40NjUxMTkwNSAxNy4zODk0MDQ4LDYuNDkzNTExOSBDMTcuMzg5NDA0OCwzLjc0MjAyMzgxIDE1LjczODUxMTksMi4zMTEyNSAxNC43NDc5NzYyLDEuNTQwODMzMzMgTDE2Ljk0OTE2NjcsMS41NDA4MzMzMyBMMTguNjAwMDU5NSwtMC4xMTAwNTk1MjQgTDE4LjYwMDA1OTUsLTAuMTEwMDU5NTI0IFogTTE2LjA2ODY5MDUsMjMuNjYyNzk3NiBDMTYuMDY4NjkwNSwyNS44NjM5ODgxIDE0LjA4NzYxOSwyNy44NDUwNTk1IDEwLjM0NTU5NTIsMjcuODQ1MDU5NSBDNi4xNjMzMzMzMywyNy44NDUwNTk1IDMuNTIxOTA0NzYsMjUuNzUzOTI4NiAzLjUyMTkwNDc2LDIzLjExMjUgQzMuNTIxOTA0NzYsMjAuMzYxMDExOSA2LjA1MzI3MzgxLDE5LjM3MDQ3NjIgNi44MjM2OTA0OCwxOS4wNDAyOTc2IEM4LjQ3NDU4MzMzLDE4LjQ5IDEwLjU2NTcxNDMsMTguMzc5OTQwNSAxMC44OTU4OTI5LDE4LjM3OTk0MDUgTDExLjc3NjM2OSwxOC4zNzk5NDA1IEMxNC43NDc5NzYyLDIwLjU4MTEzMSAxNi4wNjg2OTA1LDIxLjY4MTcyNjIgMTYuMDY4NjkwNSwyMy42NjI3OTc2IEwxNi4wNjg2OTA1LDIzLjY2Mjc5NzYgWiBNMTAuNTY1NzE0MywxMi4xMDY1NDc2IEM3LjI2MzkyODU3LDEyLjEwNjU0NzYgNS41MDI5NzYxOSw4LjI1NDQ2NDI5IDUuNTAyOTc2MTksNS4yODI4NTcxNCBDNS41MDI5NzYxOSwyLjUzMTM2OTA1IDcuMjYzOTI4NTcsMS4yMTA2NTQ3NiA5LjEzNDk0MDQ4LDEuMjEwNjU0NzYgQzEyLjY1Njg0NTIsMS4yMTA2NTQ3NiAxNC40MTc3OTc2LDUuNjEzMDM1NzEgMTQuNDE3Nzk3Niw4LjI1NDQ2NDI5IEMxNC4zMDc3MzgxLDExLjQ0NjE5MDUgMTEuNTU2MjUsMTIuMTA2NTQ3NiAxMC41NjU3MTQzLDEyLjEwNjU0NzYgTDEwLjU2NTcxNDMsMTIuMTA2NTQ3NiBaIE0yNi40MTQyODU3LDEyLjk4NzAyMzggTDI2LjQxNDI4NTcsOC4wMzQzNDUyNCBMMjQuNzYzMzkyOSw4LjAzNDM0NTI0IEwyNC43NjMzOTI5LDEyLjk4NzAyMzggTDE5LjgxMDcxNDMsMTIuOTg3MDIzOCBMMTkuODEwNzE0MywxNC42Mzc5MTY3IEwyNC43NjMzOTI5LDE0LjYzNzkxNjcgTDI0Ljc2MzM5MjksMTkuNTkwNTk1MiBMMjYuNDE0Mjg1NywxOS41OTA1OTUyIEwyNi40MTQyODU3LDE0LjYzNzkxNjcgTDMxLjM2Njk2NDMsMTQuNjM3OTE2NyBMMzEuMzY2OTY0MywxMi45ODcwMjM4IEwyNi40MTQyODU3LDEyLjk4NzAyMzggTDI2LjQxNDI4NTcsMTIuOTg3MDIzOCBaIiBpZD0iU2hhcGUiIG9wYWNpdHk9IjAuMTYiIGZpbGw9IiMzRTI3MjMiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgTDEwLjU2NTcxNDMsLTAuNDQwMjM4MDk1IEM0Ljk1MjY3ODU3LC0wLjQ0MDIzODA5NSAyLjIwMTE5MDQ4LDIuOTcxNjA3MTQgMi4yMDExOTA0OCw2LjgyMzY5MDQ4IEMyLjIwMTE5MDQ4LDkuNzk1Mjk3NjIgNC42MjI1LDEzLjA5NzA4MzMgOC42OTQ3MDIzOCwxMy4wOTcwODMzIEw5LjY4NTIzODEsMTMuMDk3MDgzMyBDOS40NjUxMTkwNSwxMy40MjcyNjE5IDkuMzU1MDU5NTIsMTQuMDg3NjE5IDkuMzU1MDU5NTIsMTQuNTI3ODU3MSBDOS4zNTUwNTk1MiwxNS42Mjg0NTI0IDkuNzk1Mjk3NjIsMTYuMTc4NzUgMTAuNDU1NjU0OCwxNi44MzkxMDcxIEM4LjY5NDcwMjM4LDE2Ljk0OTE2NjcgNS4zOTI5MTY2NywxNy4yNzkzNDUyIDIuOTcxNjA3MTQsMTguODIwMTc4NiBDMC42NjAzNTcxNDMsMjAuMTQwODkyOSAwLDIyLjEyMTk2NDMgMCwyMy41NTI3MzgxIEMwLDI2LjQxNDI4NTcgMi43NTE0ODgxLDI5LjA1NTcxNDMgOC4zNjQ1MjM4MSwyOS4wNTU3MTQzIEMxNS4wNzgxNTQ4LDI5LjA1NTcxNDMgMTguNjAwMDU5NSwyNS40MjM3NSAxOC42MDAwNTk1LDIxLjc5MTc4NTcgQzE4LjYwMDA1OTUsMTkuMDQwMjk3NiAxNy4wNTkyMjYyLDE3LjcxOTU4MzMgMTUuMjk4MjczOCwxNi4yODg4MDk1IEwxMy44Njc1LDE1LjE4ODIxNDMgQzEzLjQyNzI2MTksMTQuODU4MDM1NyAxMi45ODcwMjM4LDE0LjQxNzc5NzYgMTIuOTg3MDIzOCwxMy41MzczMjE0IEMxMi45ODcwMjM4LDEyLjY1Njg0NTIgMTMuNTM3MzIxNCwxMS45OTY0ODgxIDE0LjA4NzYxOSwxMS42NjYzMDk1IEMxNS43Mzg1MTE5LDEwLjM0NTU5NTIgMTcuMzg5NDA0OCw5LjAyNDg4MDk1IDE3LjM4OTQwNDgsNi4wNTMyNzM4MSBDMTcuMzg5NDA0OCwzLjMwMTc4NTcxIDE1LjczODUxMTksMS44NzEwMTE5IDE0Ljc0Nzk3NjIsMS4xMDA1OTUyNCBMMTYuOTQ5MTY2NywxLjEwMDU5NTI0IEwxOC42MDAwNTk1LC0wLjQ0MDIzODA5NSBMMTguNjAwMDU5NSwtMC40NDAyMzgwOTUgWiBNMTYuMDY4NjkwNSwyMy4zMzI2MTkgQzE2LjA2ODY5MDUsMjUuNTMzODA5NSAxNC4wODc2MTksMjcuNTE0ODgxIDEwLjM0NTU5NTIsMjcuNTE0ODgxIEM2LjE2MzMzMzMzLDI3LjUxNDg4MSAzLjUyMTkwNDc2LDI1LjQyMzc1IDMuNTIxOTA0NzYsMjIuNzgyMzIxNCBDMy41MjE5MDQ3NiwyMC4wMzA4MzMzIDYuMDUzMjczODEsMTkuMDQwMjk3NiA2LjgyMzY5MDQ4LDE4LjcxMDExOSBDOC40NzQ1ODMzMywxOC4xNTk4MjE0IDEwLjU2NTcxNDMsMTguMDQ5NzYxOSAxMC44OTU4OTI5LDE4LjA0OTc2MTkgTDExLjc3NjM2OSwxOC4wNDk3NjE5IEMxNC43NDc5NzYyLDIwLjI1MDk1MjQgMTYuMDY4NjkwNSwyMS4zNTE1NDc2IDE2LjA2ODY5MDUsMjMuMzMyNjE5IEwxNi4wNjg2OTA1LDIzLjMzMjYxOSBaIE0xMC41NjU3MTQzLDExLjg4NjQyODYgQzcuMjYzOTI4NTcsMTEuODg2NDI4NiA1LjUwMjk3NjE5LDguMDM0MzQ1MjQgNS41MDI5NzYxOSw1LjA2MjczODEgQzUuNTAyOTc2MTksMi4zMTEyNSA3LjI2MzkyODU3LDAuOTkwNTM1NzE0IDkuMTM0OTQwNDgsMC45OTA1MzU3MTQgQzEyLjY1Njg0NTIsMC45OTA1MzU3MTQgMTQuNDE3Nzk3Niw1LjM5MjkxNjY3IDE0LjQxNzc5NzYsOC4wMzQzNDUyNCBDMTQuMzA3NzM4MSwxMS4yMjYwNzE0IDExLjU1NjI1LDExLjg4NjQyODYgMTAuNTY1NzE0MywxMS44ODY0Mjg2IEwxMC41NjU3MTQzLDExLjg4NjQyODYgWiBNMjYuNDE0Mjg1NywxMi42NTY4NDUyIEwyNi40MTQyODU3LDcuNzA0MTY2NjcgTDI0Ljc2MzM5MjksNy43MDQxNjY2NyBMMjQuNzYzMzkyOSwxMi42NTY4NDUyIEwxOS44MTA3MTQzLDEyLjY1Njg0NTIgTDE5LjgxMDcxNDMsMTQuMzA3NzM4MSBMMjQuNzYzMzkyOSwxNC4zMDc3MzgxIEwyNC43NjMzOTI5LDE5LjI2MDQxNjcgTDI2LjQxNDI4NTcsMTkuMjYwNDE2NyBMMjYuNDE0Mjg1NywxNC4zMDc3MzgxIEwzMS4zNjY5NjQzLDE0LjMwNzczODEgTDMxLjM2Njk2NDMsMTIuNjU2ODQ1MiBMMjYuNDE0Mjg1NywxMi42NTY4NDUyIEwyNi40MTQyODU3LDEyLjY1Njg0NTIgWiIgaWQ9IlNoYXBlIiBmaWxsPSIjRjFGMUYxIj48L3BhdGg+CiAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTkuNzk1Mjk3NjIsMTMuMzE3MjAyNCBMOS43OTUyOTc2MiwxMy4wOTcwODMzIEM5LjU3NTE3ODU3LDEzLjQyNzI2MTkgOS40NjUxMTkwNSwxNC4wODc2MTkgOS40NjUxMTkwNSwxNC41Mjc4NTcxIEw5LjQ2NTExOTA1LDE0LjYzNzkxNjcgQzkuNDY1MTE5MDUsMTQuMTk3Njc4NiA5LjU3NTE3ODU3LDEzLjY0NzM4MSA5Ljc5NTI5NzYyLDEzLjMxNzIwMjQgTDkuNzk1Mjk3NjIsMTMuMzE3MjAyNCBaIE0xMC40NTU2NTQ4LDE2Ljk0OTE2NjcgQzguNjk0NzAyMzgsMTcuMDU5MjI2MiA1LjM5MjkxNjY3LDE3LjM4OTQwNDggMi45NzE2MDcxNCwxOC45MzAyMzgxIEMwLjY2MDM1NzE0MywyMC4yNTA5NTI0IDAsMjIuMjMyMDIzOCAwLDIzLjY2Mjc5NzYgTDAsMjMuNzcyODU3MSBDMC4xMTAwNTk1MjQsMjIuMzQyMDgzMyAwLjc3MDQxNjY2NywyMC40NzEwNzE0IDIuOTcxNjA3MTQsMTkuMTUwMzU3MSBDNS4zOTI5MTY2NywxNy43MTk1ODMzIDguNjk0NzAyMzgsMTcuMjc5MzQ1MiAxMC40NTU2NTQ4LDE3LjE2OTI4NTcgTDEwLjQ1NTY1NDgsMTYuOTQ5MTY2NyBMMTAuNDU1NjU0OCwxNi45NDkxNjY3IFogTTEwLjM0NTU5NTIsMjcuNTE0ODgxIEM2LjI3MzM5Mjg2LDI3LjUxNDg4MSAzLjYzMTk2NDI5LDI1LjUzMzgwOTUgMy41MjE5MDQ3NiwyMi44OTIzODEgTDMuNTIxOTA0NzYsMjMuMDAyNDQwNSBDMy41MjE5MDQ3NiwyNS42NDM4NjkgNi4xNjMzMzMzMywyNy43MzUgMTAuMzQ1NTk1MiwyNy43MzUgQzE0LjA4NzYxOSwyNy43MzUgMTYuMDY4NjkwNSwyNS43NTM5Mjg2IDE2LjA2ODY5MDUsMjMuNTUyNzM4MSBMMTYuMDY4NjkwNSwyMy40NDI2Nzg2IEMxNS45NTg2MzEsMjUuNjQzODY5IDEzLjk3NzU1OTUsMjcuNTE0ODgxIDEwLjM0NTU5NTIsMjcuNTE0ODgxIEwxMC4zNDU1OTUyLDI3LjUxNDg4MSBaIE0xNC4zMDc3MzgxLDguMjU0NDY0MjkgTDE0LjMwNzczODEsOC4xNDQ0MDQ3NiBDMTQuMTk3Njc4NiwxMS4zMzYxMzEgMTEuNTU2MjUsMTEuODg2NDI4NiAxMC40NTU2NTQ4LDExLjg4NjQyODYgQzcuMjYzOTI4NTcsMTEuODg2NDI4NiA1LjM5MjkxNjY3LDguMTQ0NDA0NzYgNS4zOTI5MTY2Nyw1LjE3Mjc5NzYyIEw1LjM5MjkxNjY3LDUuMjgyODU3MTQgQzUuMzkyOTE2NjcsOC4yNTQ0NjQyOSA3LjE1Mzg2OTA1LDEyLjEwNjU0NzYgMTAuNDU1NjU0OCwxMi4xMDY1NDc2IEMxMS41NTYyNSwxMi4xMDY1NDc2IDE0LjMwNzczODEsMTEuNDQ2MTkwNSAxNC4zMDc3MzgxLDguMjU0NDY0MjkgTDE0LjMwNzczODEsOC4yNTQ0NjQyOSBaIE0xNS40MDgzMzMzLDE2LjI4ODgwOTUgTDEzLjk3NzU1OTUsMTUuMTg4MjE0MyBDMTMuNTM3MzIxNCwxNC44NTgwMzU3IDEzLjIwNzE0MjksMTQuNDE3Nzk3NiAxMy4wOTcwODMzLDEzLjY0NzM4MSBMMTMuMDk3MDgzMywxMy43NTc0NDA1IEMxMy4wOTcwODMzLDE0LjYzNzkxNjcgMTMuNTM3MzIxNCwxNS4wNzgxNTQ4IDEzLjk3NzU1OTUsMTUuNDA4MzMzMyBMMTUuNDA4MzMzMywxNi41MDg5Mjg2IEMxNy4wNTkyMjYyLDE3LjkzOTcwMjQgMTguNjAwMDU5NSwxOS4yNjA0MTY3IDE4LjcxMDExOSwyMS43OTE3ODU3IEwxOC43MTAxMTksMjEuNjgxNzI2MiBDMTguNjAwMDU5NSwxOS4wNDAyOTc2IDE3LjA1OTIyNjIsMTcuODI5NjQyOSAxNS40MDgzMzMzLDE2LjI4ODgwOTUgTDE1LjQwODMzMzMsMTYuMjg4ODA5NSBaIE0yNi40MTQyODU3LDEyLjY1Njg0NTIgTDI2LjQxNDI4NTcsMTIuODc2OTY0MyBMMzEuMzY2OTY0MywxMi44NzY5NjQzIEwzMS4zNjY5NjQzLDEyLjY1Njg0NTIgTDI2LjQxNDI4NTcsMTIuNjU2ODQ1MiBMMjYuNDE0Mjg1NywxMi42NTY4NDUyIFogTTE3LjQ5OTQ2NDMsNi4yNzMzOTI4NiBMMTcuNDk5NDY0Myw2LjE2MzMzMzMzIEMxNy40OTk0NjQzLDMuNDExODQ1MjQgMTUuODQ4NTcxNCwxLjk4MTA3MTQzIDE0Ljg1ODAzNTcsMS4yMTA2NTQ3NiBMMTQuODU4MDM1NywxLjU0MDgzMzMzIEMxNS44NDg1NzE0LDIuMjAxMTkwNDggMTcuNDk5NDY0MywzLjYzMTk2NDI5IDE3LjQ5OTQ2NDMsNi4yNzMzOTI4NiBMMTcuNDk5NDY0Myw2LjI3MzM5Mjg2IFogTTI2LjQxNDI4NTcsNy43MDQxNjY2NyBMMjQuNzYzMzkyOSw3LjcwNDE2NjY3IEwyNC43NjMzOTI5LDcuOTI0Mjg1NzEgTDI2LjQxNDI4NTcsNy45MjQyODU3MSBMMjYuNDE0Mjg1Nyw3LjcwNDE2NjY3IEwyNi40MTQyODU3LDcuNzA0MTY2NjcgWiBNMi4zMTEyNSw2LjgyMzY5MDQ4IEwyLjMxMTI1LDYuOTMzNzUgQzIuNDIxMzA5NTIsMy4wODE2NjY2NyA1LjA2MjczODEsLTAuMTEwMDU5NTI0IDEwLjU2NTcxNDMsLTAuMTEwMDU5NTI0IEwxOC4zNzk5NDA1LC0wLjExMDA1OTUyNCBMMTguNzEwMTE5LC0wLjQ0MDIzODA5NSBMMTAuNjc1NzczOCwtMC40NDAyMzgwOTUgQzQuOTUyNjc4NTcsLTAuNDQwMjM4MDk1IDIuMzExMjUsMi45NzE2MDcxNCAyLjMxMTI1LDYuODIzNjkwNDggTDIuMzExMjUsNi44MjM2OTA0OCBaIE0xOS44MTA3MTQzLDEyLjk4NzAyMzggTDI0Ljc2MzM5MjksMTIuOTg3MDIzOCBMMjQuNzYzMzkyOSwxMi43NjY5MDQ4IEwxOS44MTA3MTQzLDEyLjc2NjkwNDggTDE5LjgxMDcxNDMsMTIuOTg3MDIzOCBMMTkuODEwNzE0MywxMi45ODcwMjM4IFoiIGlkPSJTaGFwZSIgZmlsbC1vcGFjaXR5PSIwLjY0IiBmaWxsPSIjRkZGRkZGIj48L3BhdGg+CiAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgIDwvZz4KICAgICAgICA8L2c+CiAgICA8L2c+Cjwvc3ZnPg==);
}
.btn-icon-local {
background-color: #84B6EF;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+Cjxzdmcgd2lkdGg9IjI0cHgiIGhlaWdodD0iMjBweCIgdmlld0JveD0iMCAwIDI0IDIwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHhtbG5zOnNrZXRjaD0iaHR0cDovL3d3dy5ib2hlbWlhbmNvZGluZy5jb20vc2tldGNoL25zIj4KICAgIDwhLS0gR2VuZXJhdG9yOiBTa2V0Y2ggMy4zLjIgKDEyMDQzKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT5SZWN0YW5nbGUgMjkxICsgUGF0aCAyMzI8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz48L2RlZnM+CiAgICA8ZyBpZD0iUGFnZS0xIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj4KICAgICAgICA8ZyBpZD0iQS4xLVZlcmlmeS1FbWFpbC1TY3JlZW5fbG9naW4tIiBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNDA5LjAwMDAwMCwgLTIwOS4wMDAwMDApIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZT0iI0ZGRkZGRiI+CiAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMzktQ29weS02LSstRW1haWwtQ29weS0rLVJlY3RhbmdsZS0yOTAtKy1SZWN0YW5nbGUtMjkxLSstUGF0aC0yMzIiIHNrZXRjaDp0eXBlPSJNU0xheWVyR3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQwMC4wMDAwMDAsIDE5Ny4wMDAwMDApIj4KICAgICAgICAgICAgICAgIDxnIGlkPSJSZWN0YW5nbGUtMjkwLSstUmVjdGFuZ2xlLTI5MS0rLVBhdGgtMjMyIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj4KICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iUmVjdGFuZ2xlLTI5MS0rLVBhdGgtMjMyIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg5LjAwMDAwMCwgMTIuMDAwMDAwKSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxnPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHJlY3QgaWQ9IlJlY3RhbmdsZS0yOTEiIHg9IjAiIHk9IjAiIHdpZHRoPSIyNCIgaGVpZ2h0PSIxOS4zNSI+PC9yZWN0PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTAsMS45MjcyNzEzOSBMMTEuNjExMzAxOSwxMi45IEwyNCwxLjE5MjYyODgxIiBpZD0iUGF0aC0yMzIiPjwvcGF0aD4KICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgICAgIDwvZz4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+);
}
.btn-icon-coreos {
/* B&W CoreOS SVG logo */
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBmaWxsPSIjNjY2IiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDIxNSAyMTUiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KICA8Zz4NCiAgICA8Zz4NCiAgICAgIDxwYXRoIGQ9Ik0xMDcuNDc5LDEuMDc1Yy01OC42NzcsMC0xMDYuNDA0LDQ3LjczLTEwNi40MDQsMTA2LjM5OGMwLDU4LjY3Miw0Ny43MjcsMTA2LjM5OSwxMDYuNDA0LDEwNi4zOTkNCiAgICAgICAgICAgICAgYzU4LjY1OSwwLDEwNi4zOS00Ny43MjcsMTA2LjM5LTEwNi4zOTlDMjEzLjg2OSw0OC44MDUsMTY2LjEzOCwxLjA3NSwxMDcuNDc5LDEuMDc1eiBNMTQ3LjQ0OSwxMzQuNjI3DQogICAgICAgICAgICAgIGMtMC44OCwwLjEyOC0xLjc0OSwwLjI1MS0yLjYzMiwwLjM2NGMtOC4wMywxLjAzOC0xNi42MDIsMS43NDMtMjUuNTYxLDIuMDc4Yy0zLjg1NiwwLjE0NC03Ljc5MywwLjIzMS0xMS43NzYsMC4yMzENCiAgICAgICAgICAgICAgYy0zLjk5NSwwLTcuOTItMC4wODYtMTEuNzg4LTAuMjMxYy04Ljk0OC0wLjMzNS0xNy41MjYtMS4wNC0yNS41NDktMi4wNzhjLTAuNzE2LTUuOTg3LTEuMjAxLTEyLjIxNi0xLjQ0My0xOC42MjMNCiAgICAgICAgICAgICAgYy0wLjExNi0yLjkyNi0wLjE3My01Ljg5Ni0wLjE3My04Ljg5NWMwLTMuMDAyLDAuMDU3LTUuOTY2LDAuMTczLTguODk4YzAuMjQzLTYuNDA4LDAuNzI4LTEyLjYzMywxLjQ0My0xOC42Mg0KICAgICAgICAgICAgICBjMC4xNDQtMS4yNDYsMC4zMDYtMi40ODUsMC40NzMtMy43MDljNS4yNDEtMzguMDQsMTkuNzUyLTY1LjQwOCwzNi44NjMtNjUuNDA4YzUzLjM2NCwwLDk2LjYzMiw0My4yNjIsOTYuNjMzLDk2LjYzNQ0KICAgICAgICAgICAgICBDMjA0LjExMiwxMTkuNTQ3LDE4MC44NjYsMTI5LjkzNCwxNDcuNDQ5LDEzNC42Mjd6Ii8+DQogICAgICA8cGF0aCBkPSJNMTQ3LjQ0OCw4MC4zMTZjLTAuOTY5LTEuNDE0LTIuMDA5LTIuNzY4LTMuMTE3LTQuMDY5Yy04Ljg2Ni0xMC40NTEtMjIuMDc0LTE3LjA5Mi0zNi44NTItMTcuMDkyDQogICAgICAgICAgICAgIGMtNC43OTEsMC05LjA1Nyw3LjMzMy0xMS43ODgsMTguNzJjLTEuMDg1LDQuNTQtMS45MjgsOS43MjEtMi40NywxNS4zNDNjLTAuNDI4LDQuNTA1LTAuNjU4LDkuMjk3LTAuNjU4LDE0LjI1NQ0KICAgICAgICAgICAgICBzMC4yMzEsOS43NTEsMC42NTgsMTQuMjUyYzQuNTA4LDAuNDI4LDkuMjkzLDAuNjU3LDE0LjI1OCwwLjY1N2M0Ljk1OSwwLDkuNzQ0LTAuMjMsMTQuMjUyLTAuNjU3DQogICAgICAgICAgICAgIGM5LjkxMS0wLjk0LDE4LjQ2Ni0yLjg0NiwyNC41MjctNS4zNTdjNS45ODYtMi40NzYsOS41MjgtNS41NTksOS41MjgtOC44OTVDMTU1Ljc4Niw5Ny40MDcsMTUyLjcxMiw4OC4wNTcsMTQ3LjQ0OCw4MC4zMTZ6Ii8+DQogICAgPC9nPg0KICA8L2c+DQo8L3N2Zz4NCg==);
}
.btn-icon-github {
background-color: #F5F5F5;
background-image: url(data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9IjE2IiB3aWR0aD0iMTYiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CiAgPHBhdGggZD0iTTggMEMzLjU4IDAgMCAzLjU4IDAgOGMwIDMuNTQgMi4yOSA2LjUzIDUuNDcgNy41OSAwLjQgMC4wNyAwLjU1LTAuMTcgMC41NS0wLjM4IDAtMC4xOS0wLjAxLTAuODItMC4wMS0xLjQ5LTIuMDEgMC4zNy0yLjUzLTAuNDktMi42OS0wLjk0LTAuMDktMC4yMy0wLjQ4LTAuOTQtMC44Mi0xLjEzLTAuMjgtMC4xNS0wLjY4LTAuNTItMC4wMS0wLjUzIDAuNjMtMC4wMSAxLjA4IDAuNTggMS4yMyAwLjgyIDAuNzIgMS4yMSAxLjg3IDAuODcgMi4zMyAwLjY2IDAuMDctMC41MiAwLjI4LTAuODcgMC41MS0xLjA3LTEuNzgtMC4yLTMuNjQtMC44OS0zLjY0LTMuOTUgMC0wLjg3IDAuMzEtMS41OSAwLjgyLTIuMTUtMC4wOC0wLjItMC4zNi0xLjAyIDAuMDgtMi4xMiAwIDAgMC42Ny0wLjIxIDIuMiAwLjgyIDAuNjQtMC4xOCAxLjMyLTAuMjcgMi0wLjI3IDAuNjggMCAxLjM2IDAuMDkgMiAwLjI3IDEuNTMtMS4wNCAyLjItMC44MiAyLjItMC44MiAwLjQ0IDEuMSAwLjE2IDEuOTIgMC4wOCAyLjEyIDAuNTEgMC41NiAwLjgyIDEuMjcgMC44MiAyLjE1IDAgMy4wNy0xLjg3IDMuNzUtMy42NSAzLjk1IDAuMjkgMC4yNSAwLjU0IDAuNzMgMC41NCAxLjQ4IDAgMS4wNy0wLjAxIDEuOTMtMC4wMSAyLjIgMCAwLjIxIDAuMTUgMC40NiAwLjU1IDAuMzhDMTMuNzEgMTQuNTMgMTYgMTEuNTMgMTYgOCAxNiAzLjU4IDEyLjQyIDAgOCAweiIgLz4KPC9zdmc+Cg==);
}
.btn-icon-bitbucket {
background-color: #205081;
background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwMCIgd2lkdGg9Ijc4NS43MTQiPjxwYXRoIGQ9Ik00NTQuNzcgNDc5LjM4NnE0LjQ2NCAzNS4xNTQgLTI4LjE3OSA1Ni4zNTh0LTYyLjIxNyAzLjM0OHEtMjEuNzYyIC05LjQ4NiAtMjkuODUzIC0zMi4zNjR0LS4yNzkgLTQ1Ljc1NiAyOS4wMTYgLTMyLjM2NHEyMC4wODggLTEwLjA0NCA0MC40NTUgLTYuNjk2dDM1LjcxMiAxOS44MDkgMTUuMzQ1IDM3LjY2NXptNjEuOTM4IC0xMS43MThxLTcuODEyIC01OS43MDYgLTYzLjA1NCAtOTEuNTEydC0xMDkuOTI2IC03LjI1NHEtMzUuMTU0IDE1LjYyNCAtNTYuMDc5IDQ5LjM4M3QtMTkuMjUxIDcyLjI2MXEyLjIzMiA1MC43NzggNDMuMjQ1IDg2LjQ5dDkyLjM0OSAzMS4yNDhxNTAuNzc4IC00LjQ2NCA4NC44MTYgLTQ2Ljg3MnQyNy45IC05My43NDR6bTEzMy4zNjIgLTMwMi40MzZxLTExLjE2IC0xNS4wNjYgLTMxLjI0OCAtMjQuODMxdC0zMi4zNjQgLTEyLjI3NiAtMzkuNjE4IC02Ljk3NXEtMTYyLjM3OCAtMjYuMjI2IC0zMTUuODI4IDEuMTE2IC0yMy45OTQgMy45MDYgLTM2LjgyOCA2LjY5NnQtMzAuNjkgMTIuMjc2IC0yNy45IDIzLjk5NHExNi43NCAxNS42MjQgNDIuNDA4IDI1LjM4OXQ0MS4wMTMgMTIuMjc2IDQ4LjgyNSA2LjQxN3ExMjcuMjI0IDE2LjE4MiAyNDkuOTg0IC41NTggMzUuMTU0IC00LjQ2NCA0OS45NDEgLTYuNjk2dDQwLjQ1NSAtMTEuOTk3IDQxLjg1IC0yNS45NDd6bTMxLjgwNiA1NzcuNTNxLTQuNDY0IDE0LjUwOCAtOC42NDkgNDIuNjg3dC03LjgxMiA0Ni44NzIgLTE1LjkwMyAzOS4wNiAtMzIuMzY0IDMxLjUyN3EtNDcuOTg4IDI2Ljc4NCAtMTA1Ljc0MSAzOS44OTd0LTExMi43MTYgMTIuMjc2IC0xMTIuNDM3IC0xMC4zMjNxLTI1LjY2OCAtNC40NjQgLTQ1LjQ3NyAtMTAuMDQ0dC00Mi42ODcgLTE1LjA2NiAtNDAuNzM0IC0yNC4yNzMgLTI5LjAxNiAtMzQuMzE3cS0xMy45NSAtNTMuNTY4IC0zMS44MDYgLTE2Mi45MzZsMy4zNDggLTguOTI4IDEwLjA0NCAtNS4wMjJxMTI0LjQzNCA4Mi41ODQgMjgyLjYyNyA4Mi41ODR0MjgzLjE4NSAtODIuNTg0cTExLjcxOCAzLjM0OCAxMy4zOTIgMTIuODM0dC0yLjc5IDI1LjExIC00LjQ2NCAyMC42NDZ6bTEwMC45OTggLTUzNi4yMzhxLTE0LjUwOCA5My4xODYgLTYxLjkzOCAzNjUuNDkgLTIuNzkgMTYuNzQgLTE1LjA2NiAzMS4yNDh0LTI0LjI3MyAyMi4zMiAtMzAuNDExIDE3LjI5OHEtMTQwLjYxNiA3MC4zMDggLTM0MC4zOCA0OS4xMDQgLTEzOC4zODQgLTE1LjA2NiAtMjE5Ljg1MiAtNzcuNTYyIC04LjM3IC02LjY5NiAtMTQuMjI5IC0xNC43ODd0LTkuNDg2IC0xOS41MyAtNS4wMjIgLTE4Ljk3MiAtMy4zNDggLTIyLjA0MSAtMy4wNjkgLTE5LjUzcS01LjAyMiAtMjcuOSAtMTQuNzg3IC04My43dC0xNS42MjQgLTkwLjExNyAtMTMuMTEzIC04Mi4zMDUgLTEyLjI3NiAtODguMTY0cTEuNjc0IC0xNC41MDggOS43NjUgLTI3LjA2M3QxNy41NzcgLTIwLjkyNSAyNS4xMSAtMTYuNzQgMjUuNjY4IC0xMi41NTUgMjYuNzg0IC0xMC4zMjNxNjkuNzUgLTI1LjY2OCAxNzQuNjU0IC0zNS43MTIgMjExLjQ4MiAtMjAuNjQ2IDM3Ny4yMDggMjcuOSA4Ni40OSAyNS42NjggMTE5Ljk3IDY4LjA3NiA4LjkyOCAxMS4xNiA5LjIwNyAyOC40NTh0LTMuMDY5IDMwLjEzMnoiIGZpbGw9IiNGRkZGRkYiLz48L3N2Zz4K);
}
.btn-text {
line-height: 36px;
padding: 6px 12px;
text-align: center;
font-weight: 600;
}
.form-row {
display: block;
margin: 20px auto;
}
label {
font-size: 13px;
font-weight: 600;
}
.input-box {
display: block;
height: 36px;
padding: 6px 12px;
font-size: 14px;
line-height: 1.42857143;
color: #666;
border: 1px solid #CCC;
border-radius: 4px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
width: 250px;
margin: auto;
}
.input-box:focus,
.input-box:active {
outline: none;
border-color: #66AFE9;
}
.error-box-field,
.error-box {
background-color: #DD1327;
max-width: 320px;
color: #fff;
font-size: 14px;
font-weight: normal;
padding: 4px 0;
}
.error-box {
margin: 20px auto;
}
.error-box-field {
margin: 0 auto;
width: 250px;
}
.instruction-block {
font-size: 14px;
}
.detail-block {
color: #777;
font-size: 12px;
margin-top: 20px;
}
.bullet-point {
list-style: square;
}
.list-with-title {
text-align: left;
margin: 0 25%;
}
.hr {
color: #999;
}
</style>
</head>
<body>
<div id="navbar">
<div id="navbar-logo-wrap">
<img id="navbar-logo" src="{{ .LogoURL }}">
</div>
</div>
<div id="container">
{{ template "header.html" . }}
<div class="panel">
<h2 class="heading">Log in to {{ .Issuer }} </h2>
<div>
{{ range $c := .Connectors }}
<div class="form-row">
<a href="{{ $c.URL }}?state={{ $.State }}" target="_self">
<button class="btn btn-provider">
<span class="btn-icon btn-icon-{{ $c.ID }}"></span>
<span class="btn-text">Log in with {{ $c.Name }}</span>
</button>
</a>
</div>
{{ end }}
</div>
</div>
{{ template "footer.html" . }}
{{ template "header.html" . }}
<div class="panel">
<h2 class="heading">Log in to Your Account</h2>
<form method="post" action="{{ .PostURL }}">
<div class="form-row">
<div class="input-desc">
<label for="userid">Username</label>
</div>
<input tabindex="1" required id="login" name="login" type="text" class="input-box" placeholder="username" {{ if .Username }}value="{{ .Username }}" {{ else }} autofocus {{ end }}/>
</div>
<div class="form-row">
<div class="input-desc">
<label for="password">Password</label>
</div>
<input tabindex="2" required id="password" name="password" type="password" class="input-box" placeholder="password" {{ if .Invalid }} autofocus {{ end }}/>
</div>
<input type="hidden" name="state" value="{{ .State }}"/>
{{ if .Invalid }}
<div class="error-box">
Invalid username and password.
</div>
{{ end }}
<button tabindex="3" type="submit" class="btn btn-primary">Login</button>
</form>
</div>
{{ template "footer.html" . }}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment