- {{ if (not .User.TOTPEnabled) }}
+ {{ if (not .User.IsTOTPEnabled) }}
TOTP is required to access these.
diff --git a/internal/dis/component/auth/totp.go b/internal/dis/component/auth/totp.go
index 0706bf9..285ded7 100644
--- a/internal/dis/component/auth/totp.go
+++ b/internal/dis/component/auth/totp.go
@@ -22,11 +22,11 @@ func (auth *Auth) authTOTPEnable(ctx context.Context) http.Handler {
},
FieldTemplate: httpx.PureCSSFieldTemplate,
- CSRF: auth.csrf.Get(nil),
+ CSRF: auth.CSRF(),
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
user, err := auth.UserOf(r)
- return struct{}{}, err == nil && user != nil && user.TOTPEnabled
+ return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
},
RenderTemplate: totpEnableTemplate,
@@ -81,11 +81,11 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
},
FieldTemplate: httpx.PureCSSFieldTemplate,
- CSRF: auth.csrf.Get(nil),
+ CSRF: auth.CSRF(),
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
- user, _ := auth.UserOf(r)
- return struct{}{}, user != nil && user.TOTPEnabled
+ user, err := auth.UserOf(r)
+ return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
},
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
user, err := auth.UserOf(r)
@@ -152,11 +152,11 @@ func (auth *Auth) authTOTPDisable(ctx context.Context) http.Handler {
},
FieldTemplate: httpx.PureCSSFieldTemplate,
- CSRF: auth.csrf.Get(nil),
+ CSRF: auth.CSRF(),
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
- user, _ := auth.UserOf(r)
- return struct{}{}, user != nil && !user.TOTPEnabled
+ user, err := auth.UserOf(r)
+ return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
},
RenderTemplate: totpDisableTemplate,
RenderTemplateContext: auth.UserFormContext,
diff --git a/internal/dis/component/auth/user.go b/internal/dis/component/auth/user.go
index 30db63f..314faae 100644
--- a/internal/dis/component/auth/user.go
+++ b/internal/dis/component/auth/user.go
@@ -4,13 +4,13 @@ import (
"bytes"
"context"
"encoding/base64"
- "errors"
"fmt"
"image/png"
"net/http"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
+ "github.com/pkg/errors"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"golang.org/x/crypto/bcrypt"
@@ -90,15 +90,17 @@ func (auth *Auth) CreateUser(ctx context.Context, name string) (user *AuthUser,
user = &AuthUser{
User: models.User{
- User: name,
- Enabled: false,
+ User: name,
},
}
+ user.SetAdmin(false)
+ user.SetEnabled(false)
+ user.SetTOTPEnabled(false)
// do the create statement
- err = table.Create(&user.User).Error
+ err = table.Select("*").Create(&user.User).Error
if err != nil {
- return nil, err
+ return nil, errors.Wrapf(err, "Create")
}
user.auth = auth
@@ -116,7 +118,7 @@ func (au *AuthUser) String() string {
return "User{nil}"
}
hasPassword := len(au.PasswordHash) > 0
- return fmt.Sprintf("User{Name:%q,Enabled:%t,HasPassword:%t,Admin:%t}", au.User.User, au.User.Enabled, hasPassword, au.User.Admin)
+ return fmt.Sprintf("User{Name:%q,Enabled:%t,HasPassword:%t,Admin:%t}", au.User.User, au.User.IsEnabled(), hasPassword, au.User.IsAdmin())
}
var (
@@ -140,7 +142,7 @@ func (au *AuthUser) CheckTOTP(passcode string) error {
return err
}
- if au.TOTPEnabled && !totp.Validate(passcode, secret.Secret()) {
+ if au.IsTOTPEnabled() && !totp.Validate(passcode, secret.Secret()) {
return ErrTOTPFailed
}
return nil
@@ -148,7 +150,7 @@ func (au *AuthUser) CheckTOTP(passcode string) error {
// NewTOTP generates a new TOTP secret, returning a totp key.
func (au *AuthUser) NewTOTP(ctx context.Context) (*otp.Key, error) {
- if au.User.TOTPEnabled {
+ if au.User.IsTOTPEnabled() {
return nil, ErrTOTPEnabled
}
@@ -191,14 +193,14 @@ func (au *AuthUser) EnableTOTP(ctx context.Context, passcode string) error {
return ErrTOTPFailed
}
- au.User.TOTPEnabled = true
+ au.User.SetTOTPEnabled(true)
return au.Save(ctx)
}
// DisableTOTP disables totp for the given user
func (au *AuthUser) DisableTOTP(ctx context.Context) (err error) {
- au.User.TOTPEnabled = false
+ au.User.SetTOTPEnabled(false)
au.User.TOTPURL = ""
return au.Save(ctx)
}
@@ -209,14 +211,14 @@ func (au *AuthUser) SetPassword(ctx context.Context, password []byte) (err error
if err != nil {
return err
}
- au.User.Enabled = true
+ au.User.SetEnabled(true)
return au.Save(ctx)
}
// UnsetPassword removes the password from this user, and disables them
func (au *AuthUser) UnsetPassword(ctx context.Context) error {
au.User.PasswordHash = nil
- au.User.Enabled = false
+ au.User.SetEnabled(false)
return au.Save(ctx)
}
@@ -230,7 +232,7 @@ func (au *AuthUser) CheckPassword(ctx context.Context, password []byte) error {
if au == nil {
return ErrNoUser
}
- if !au.User.Enabled {
+ if !au.User.IsEnabled() {
return ErrUserDisabled
}
@@ -254,14 +256,14 @@ func (au *AuthUser) CheckCredentials(ctx context.Context, password []byte, passc
// MakeAdmin makes this user an admin, and saves the update in the database.
// If the user is already an admin, does not return an error.
func (au *AuthUser) MakeAdmin(ctx context.Context) error {
- au.User.Admin = true
+ au.User.SetAdmin(true)
return au.Save(ctx)
}
// MakeRegular removes admin rights from this user.
// If this user is not an dmin, does not return an error.
func (au *AuthUser) MakeRegular(ctx context.Context) error {
- au.User.Admin = true
+ au.User.SetAdmin(false)
return au.Save(ctx)
}
@@ -271,7 +273,7 @@ func (au *AuthUser) Save(ctx context.Context) error {
if err != nil {
return err
}
- return table.Save(&au.User).Error
+ return table.Select("*").Updates(&au.User).Error
}
// Delete deletes the user from the database
diff --git a/internal/dis/component/control/admin/admin.go b/internal/dis/component/control/admin/admin.go
index b6bd97b..35c8bb2 100644
--- a/internal/dis/component/control/admin/admin.go
+++ b/internal/dis/component/control/admin/admin.go
@@ -9,6 +9,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
"github.com/julienschmidt/httprouter"
+ "github.com/rs/zerolog"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
@@ -48,6 +49,7 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
Handler: admin.serveSocket,
}
handler = admin.Dependencies.Auth.Protect(socket, auth.Admin)
+ handler = admin.Dependencies.Auth.CSRF()(handler)
}
// handle everything
@@ -61,6 +63,26 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
Template: indexTemplate,
})
+ // add a handler for the user page
+ router.Handler(http.MethodGet, route+"users", httpx.HTMLHandler[userContext]{
+ Handler: admin.users,
+ Template: userTemplate,
+ })
+
+ // add a user create form
+ {
+ create := admin.createUser(ctx)
+ router.Handler(http.MethodGet, route+"users/create", create)
+ router.Handler(http.MethodPost, route+"users/create", create)
+ }
+
+ // add all the admin actions
+ router.Handler(http.MethodPost, route+"users/delete", admin.usersDeleteHandler(ctx))
+ router.Handler(http.MethodPost, route+"users/disable", admin.usersDisableHandler(ctx))
+ router.Handler(http.MethodPost, route+"users/disabletotp", admin.usersDisableTOTPHandler(ctx))
+ router.Handler(http.MethodPost, route+"users/password", admin.usersPasswordHandler(ctx))
+ router.Handler(http.MethodPost, route+"users/toggleadmin", admin.usersToggleAdmin(ctx))
+
// add a handler for the component page
router.Handler(http.MethodGet, route+"components", httpx.HTMLHandler[componentContext]{
Handler: admin.components,
@@ -79,9 +101,19 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
Template: instanceTemplate,
})
- router.Handler(http.MethodPost, route+"api/login", httpx.RedirectHandler(func(r *http.Request) (string, int, error) {
+ // add a router for the login page
+ router.Handler(http.MethodPost, route+"login", admin.loginHandler(ctx))
+
+ return
+}
+
+func (admin *Admin) loginHandler(ctx context.Context) http.Handler {
+ logger := zerolog.Ctx(ctx)
+
+ return httpx.RedirectHandler(func(r *http.Request) (string, int, error) {
// parse the form
if err := r.ParseForm(); err != nil {
+ logger.Err(err).Msg("failed to parse admin login")
return "", 0, err
}
@@ -93,10 +125,9 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
target, err := instance.Users().Login(r.Context(), nil, r.PostFormValue("user"))
if err != nil {
+ logger.Err(err).Msg("failed to admin login")
return "", 0, err
}
return target.String(), http.StatusSeeOther, err
- }))
-
- return
+ })
}
diff --git a/internal/dis/component/control/admin/html/index.html b/internal/dis/component/control/admin/html/index.html
index 4909062..8472c44 100644
--- a/internal/dis/component/control/admin/html/index.html
+++ b/internal/dis/component/control/admin/html/index.html
@@ -6,7 +6,10 @@
Admin
- Components
+
{{ end }}
diff --git a/internal/dis/component/control/admin/html/instance.html b/internal/dis/component/control/admin/html/instance.html
index 4fed81f..167120a 100644
--- a/internal/dis/component/control/admin/html/instance.html
+++ b/internal/dis/component/control/admin/html/instance.html
@@ -7,7 +7,9 @@
Instance
- Ingredients
+
{{ end }}
@@ -216,6 +218,7 @@
{{ $slug := .Instance.Slug }}
+ {{ $csrf := .CSRF }}
{{ range $index, $user := .Info.Users }}
@@ -247,10 +250,11 @@
{{ $user.Login.Time.Format "2006-01-02T15:04:05Z07:00" }}
-
diff --git a/internal/dis/component/control/admin/html/user_create.html b/internal/dis/component/control/admin/html/user_create.html
new file mode 100644
index 0000000..91b8fb6
--- /dev/null
+++ b/internal/dis/component/control/admin/html/user_create.html
@@ -0,0 +1,11 @@
+{{ template "_form.html" . }}
+{{ define "form/title" }}Distillery Admin - Create User{{ end }}
+{{ define "form/button" }}Create{{ end }}
+
+{{ define "header"}}
+
+ Control >
+ Users >
+ Create
+
+{{ end }}
diff --git a/internal/dis/component/control/admin/html/users.html b/internal/dis/component/control/admin/html/users.html
new file mode 100644
index 0000000..0957103
--- /dev/null
+++ b/internal/dis/component/control/admin/html/users.html
@@ -0,0 +1,95 @@
+{{ template "_base.html" . }}
+{{ define "title" }}Distillery Admin - Users{{ end }}
+
+{{ define "header"}}
+
+ Control >
+ Users
+
+
+
+
+{{ end }}
+
+{{ define "content" }}
+
+
+
+
+
+
+
+ Username
+
+
+ Enabled
+
+
+ Admin
+
+
+ Passcode (TOTP)
+
+
+ Actions
+
+
+
+
+ {{ $csrf := .CSRF }}
+ {{ range .Users }}
+
+
+ {{ .User.User }}
+
+
+ {{ .User.IsEnabled }}
+
+
+
+ {{ .User.IsAdmin }}
+
+
+ {{ .User.IsTOTPEnabled }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ end }}
+
+
+
+
+
+
+{{ end }}
\ No newline at end of file
diff --git a/internal/dis/component/control/admin/instance.go b/internal/dis/component/control/admin/instance.go
index 4a3a98c..cb2d4b8 100644
--- a/internal/dis/component/control/admin/instance.go
+++ b/internal/dis/component/control/admin/instance.go
@@ -2,6 +2,7 @@ package admin
import (
_ "embed"
+ "html/template"
"net/http"
"time"
@@ -10,6 +11,7 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/models"
"github.com/FAU-CDI/wisski-distillery/internal/status"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
+ "github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
@@ -23,11 +25,14 @@ var instanceTemplate = static.AssetsAdmin.MustParseShared(
type instanceContext struct {
Time time.Time
+ CSRF template.HTML
Instance models.Instance
Info status.WissKI
}
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
+ is.CSRF = csrf.TemplateField(r)
+
// find the instance itself!
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
if err == instances.ErrWissKINotFound {
diff --git a/internal/dis/component/control/admin/users.go b/internal/dis/component/control/admin/users.go
new file mode 100644
index 0000000..68ce422
--- /dev/null
+++ b/internal/dis/component/control/admin/users.go
@@ -0,0 +1,190 @@
+package admin
+
+import (
+ "context"
+ "errors"
+ "html/template"
+ "net/http"
+ "time"
+
+ _ "embed"
+
+ "github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
+ "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
+ "github.com/FAU-CDI/wisski-distillery/pkg/httpx"
+ "github.com/gorilla/csrf"
+ "github.com/rs/zerolog"
+)
+
+//go:embed "html/users.html"
+var userTemplateString string
+var userTemplate = static.AssetsAdmin.MustParseShared(
+ "users.html",
+ userTemplateString,
+)
+
+type userContext struct {
+ Time time.Time
+
+ CSRF template.HTML
+ Users []*auth.AuthUser
+}
+
+func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
+ uc.CSRF = csrf.TemplateField(r)
+ uc.Time = time.Now()
+ uc.Users, err = admin.Dependencies.Auth.Users(r.Context())
+ return
+}
+
+//go:embed "html/user_create.html"
+var userCreateTemplateString string
+var userCreateTemplate = static.AssetsAdmin.MustParseShared(
+ "user_create.html",
+ userCreateTemplateString,
+)
+
+var (
+ errCreateInvalidUsername = errors.New("invalid username")
+ errCreateInvalidPassword = errors.New("invalid password")
+)
+
+type createUserResult struct {
+ User string
+ Passsword string
+ Admin bool
+}
+
+func (admin *Admin) createUser(ctx context.Context) http.Handler {
+ return &httpx.Form[createUserResult]{
+ Fields: []httpx.Field{
+ {Name: "username", Type: httpx.TextField, Label: "Username"},
+ {Name: "password", Type: httpx.PasswordField, Label: "Password"},
+ {Name: "admin", Type: httpx.CheckboxField, Label: "Distillery Administrator"},
+ },
+ FieldTemplate: httpx.PureCSSFieldTemplate,
+
+ CSRF: admin.Dependencies.Auth.CSRF(),
+
+ RenderTemplate: userCreateTemplate,
+
+ Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) {
+ cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == "on"
+
+ if cu.User == "" {
+ return cu, errCreateInvalidUsername
+ }
+ if cu.Passsword == "" {
+ return cu, errCreateInvalidPassword
+ }
+
+ return cu, nil
+ },
+
+ RenderSuccess: func(cu createUserResult, values map[string]string, w http.ResponseWriter, r *http.Request) error {
+ // create the user
+ user, err := admin.Dependencies.Auth.CreateUser(r.Context(), cu.User)
+ if err != nil {
+ return err
+ }
+
+ // disable the user and setup the admin flag
+ user.SetAdmin(cu.Admin)
+ if err := user.Save(r.Context()); err != nil {
+ return err
+ }
+
+ // set the password!
+ err = user.SetPassword(r.Context(), []byte(cu.Passsword))
+ if err != nil {
+ return err
+ }
+
+ // everything went fine, redirect the user back to the user page!
+ http.Redirect(w, r, "/admin/users/", http.StatusSeeOther)
+ return nil
+ },
+ }
+}
+
+var errNotCurrentUser = httpx.Response{
+ Body: []byte("attempt to modify current user"),
+ StatusCode: http.StatusBadRequest,
+}
+
+func (admin *Admin) useraction(ctx context.Context, name string, action func(r *http.Request, user *auth.AuthUser) error) http.Handler {
+ logger := zerolog.Ctx(ctx)
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ logger.Err(err).Str("action", name).Msg("failed to parse form")
+ httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
+ return
+ }
+
+ username := r.PostFormValue("user")
+ user, err := admin.Dependencies.Auth.User(r.Context(), username)
+ if err != nil {
+ logger.Err(err).Str("action", name).Msg("failed to get user")
+ httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
+ return
+ }
+
+ me, err := admin.Dependencies.Auth.UserOf(r)
+ if err != nil {
+ logger.Err(err).Str("action", name).Msg("failed to get current user")
+ httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
+ return
+ }
+
+ // don't allow the current user
+ if me.User.User == user.User.User {
+ errNotCurrentUser.ServeHTTP(w, r)
+ return
+ }
+
+ if err := action(r, user); err != nil {
+ logger.Err(err).Str("action", name).Msg("failed to act on user")
+ httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
+ return
+ }
+
+ http.Redirect(w, r, "/admin/users/", http.StatusSeeOther)
+ })
+}
+
+func (admin *Admin) usersDeleteHandler(ctx context.Context) http.Handler {
+ return admin.useraction(ctx, "delete user", func(r *http.Request, user *auth.AuthUser) error {
+ return user.Delete(r.Context())
+ })
+}
+
+func (admin *Admin) usersDisableHandler(ctx context.Context) http.Handler {
+ return admin.useraction(ctx, "disable user", func(r *http.Request, user *auth.AuthUser) error {
+ return user.UnsetPassword(r.Context())
+ })
+}
+
+func (admin *Admin) usersDisableTOTPHandler(ctx context.Context) http.Handler {
+ return admin.useraction(ctx, "disable user totp", func(r *http.Request, user *auth.AuthUser) error {
+ return user.DisableTOTP(r.Context())
+ })
+}
+
+func (admin *Admin) usersToggleAdmin(ctx context.Context) http.Handler {
+ return admin.useraction(ctx, "toggle admin", func(r *http.Request, user *auth.AuthUser) error {
+ if user.IsAdmin() {
+ return user.MakeRegular(r.Context())
+ }
+ return user.MakeAdmin(r.Context())
+ })
+}
+
+func (admin *Admin) usersPasswordHandler(ctx context.Context) http.Handler {
+ return admin.useraction(ctx, "set password", func(r *http.Request, user *auth.AuthUser) error {
+ password := r.PostFormValue("password")
+ if password == "" {
+ return httpx.ErrBadRequest
+ }
+ return user.SetPassword(r.Context(), []byte(password))
+ })
+}
diff --git a/internal/dis/component/control/static/assets_dist.go b/internal/dis/component/control/static/assets_dist.go
index af6c995..7949dbc 100644
--- a/internal/dis/component/control/static/assets_dist.go
+++ b/internal/dis/component/control/static/assets_dist.go
@@ -5,17 +5,17 @@ package static
// AssetsHome contains assets for the 'Home' entrypoint.
var AssetsHome = Assets{
Scripts: ``,
- Styles: ` `,
+ Styles: ` `,
}
// AssetsUser contains assets for the 'User' entrypoint.
var AssetsUser = Assets{
Scripts: ``,
- Styles: ` `,
+ Styles: ` `,
}
// AssetsAdmin contains assets for the 'Admin' entrypoint.
var AssetsAdmin = Assets{
Scripts: ``,
- Styles: ` `,
+ Styles: ` `,
}
diff --git a/internal/dis/component/control/static/dist/Home.4ec77c43.css b/internal/dis/component/control/static/dist/Home.9f00501f.css
similarity index 98%
rename from internal/dis/component/control/static/dist/Home.4ec77c43.css
rename to internal/dis/component/control/static/dist/Home.9f00501f.css
index 38eb875..4a8baf4 100644
--- a/internal/dis/component/control/static/dist/Home.4ec77c43.css
+++ b/internal/dis/component/control/static/dist/Home.9f00501f.css
@@ -1 +1 @@
-html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{text-decoration:underline;border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;max-width:100%;white-space:normal;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template,[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;flex-flow:wrap;align-content:flex-start;font-family:FreeSans,Arimo,Droid Sans,Helvetica,Arial,sans-serif;display:flex}@media (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-5-12,.pure-u-10-24{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-7-12,.pure-u-14-24{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-5-8,.pure-u-15-24{width:62.5%}.pure-u-2-3,.pure-u-16-24{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-3-4,.pure-u-18-24{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-5-6,.pure-u-20-24{width:83.3333%}.pure-u-7-8,.pure-u-21-24{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;user-select:none;box-sizing:border-box;line-height:normal;display:inline-block}.pure-button::-moz-focus-inner{border:0;padding:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{color:#000c;background-color:#e6e6e6;border:#0000;border-radius:2px;padding:.5em 1em;font-family:inherit;font-size:100%;text-decoration:none}.pure-button-hover,.pure-button:hover,.pure-button:focus{background-image:linear-gradient(#0000,#0000000d 40%,#0000001a)}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{border-color:#000;box-shadow:inset 0 0 0 1px #00000026,inset 0 0 6px #0003}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{opacity:.4;cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;pointer-events:none;background-image:none;border:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{color:#fff;background-color:#0078e7}.pure-button-group .pure-button{border-right:1px solid #0003;border-radius:0;margin:0}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-right:none;border-top-right-radius:2px;border-bottom-right-radius:2px}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{vertical-align:middle;box-sizing:border-box;border:1px solid #ccc;border-radius:4px;padding:.5em .6em;display:inline-block;box-shadow:inset 0 1px 3px #ddd}.pure-form input:not([type]){box-sizing:border-box;border:1px solid #ccc;border-radius:4px;padding:.5em .6em;display:inline-block;box-shadow:inset 0 1px 3px #ddd}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus,.pure-form input:not([type]):focus{border-color:#129fea;outline:0}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:1px auto #129fea}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled],.pure-form input:not([type])[disabled]{cursor:not-allowed;color:#cad2d3;background-color:#eaeded}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{color:#777;background-color:#eee;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;background-color:#fff;border:1px solid #ccc}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{border:0;margin:0;padding:.35em 0 .75em}.pure-form legend{width:100%;color:#333;border-bottom:1px solid #e5e5e5;margin-bottom:.3em;padding:.3em 0;display:block}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea,.pure-form-stacked input:not([type]){margin:.25em 0;display:block}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-message-inline{vertical-align:middle;display:inline-block}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;vertical-align:middle;width:10em;margin:0 1em 0 0;display:inline-block}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{border-radius:0;margin:0 0 -1px;padding:10px;display:block;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{border-radius:4px 4px 0 0;margin:0;top:1px}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{border-radius:4px;margin:0;top:1px}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{border-radius:0 0 4px 4px;margin:0;top:-2px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{color:#666;vertical-align:middle;padding-left:.3em;font-size:.875em;display:inline-block}.pure-form-message{color:#666;font-size:.875em;display:block}@media only screen and (max-width:480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{text-align:left;width:100%;margin-bottom:.3em;display:block}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form-message-inline,.pure-form-message{padding:.2em 0 .8em;font-size:.75em;display:block}}.pure-menu{box-sizing:border-box}.pure-menu-fixed{z-index:3;position:fixed;top:0;left:0}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{margin:0;padding:0;list-style:none}.pure-menu-item{height:100%;margin:0;padding:0}.pure-menu-link,.pure-menu-heading{white-space:nowrap;text-decoration:none;display:block}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{vertical-align:middle;display:inline-block}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{z-index:3;margin:0;padding:0;display:none;position:absolute;top:0;left:100%}.pure-menu-horizontal .pure-menu-children{width:inherit;top:auto;left:0}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{content:"▸";padding-left:.5em;font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"▾"}.pure-menu-scrollable{overflow-x:hidden;overflow-y:scroll}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;padding:.5em 0;overflow-x:auto;overflow-y:hidden}.pure-menu-separator,.pure-menu-horizontal .pure-menu-children .pure-menu-separator{height:1px;background-color:#ccc;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{width:auto;display:block}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{cursor:default;background-color:#0000}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;text-align:center;padding:1em 0;font:italic 85%/1 arial,sans-serif}.pure-table td,.pure-table th{font-size:inherit;border-width:0 0 0 1px;border-left-style:solid;border-left-color:#cbcbcb;margin:0;padding:.5em 1em;overflow:visible}.pure-table thead{color:#000;text-align:left;vertical-align:bottom;background-color:#e0e0e0}.pure-table td{background-color:#0000}.pure-table-odd td,.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom-style:solid;border-bottom-color:#cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-xl-1-24{width:4.1667%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%}.pure-u-xl-1-5{width:20%}.pure-u-xl-5-24{width:20.8333%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%}.pure-u-xl-7-24{width:29.1667%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%}.pure-u-xl-2-5{width:40%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%}.pure-u-xl-11-24{width:45.8333%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%}.pure-u-xl-13-24{width:54.1667%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%}.pure-u-xl-3-5{width:60%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%}.pure-u-xl-17-24{width:70.8333%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%}.pure-u-xl-19-24{width:79.1667%}.pure-u-xl-4-5{width:80%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%}.pure-u-xl-23-24{width:95.8333%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}}@media screen and (min-width:120em){.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-1-2,.pure-u-xxl-1-3,.pure-u-xxl-2-3,.pure-u-xxl-1-4,.pure-u-xxl-3-4,.pure-u-xxl-1-5,.pure-u-xxl-2-5,.pure-u-xxl-3-5,.pure-u-xxl-4-5,.pure-u-xxl-5-5,.pure-u-xxl-1-6,.pure-u-xxl-5-6,.pure-u-xxl-1-8,.pure-u-xxl-3-8,.pure-u-xxl-5-8,.pure-u-xxl-7-8,.pure-u-xxl-1-12,.pure-u-xxl-5-12,.pure-u-xxl-7-12,.pure-u-xxl-11-12,.pure-u-xxl-1-24,.pure-u-xxl-2-24,.pure-u-xxl-3-24,.pure-u-xxl-4-24,.pure-u-xxl-5-24,.pure-u-xxl-6-24,.pure-u-xxl-7-24,.pure-u-xxl-8-24,.pure-u-xxl-9-24,.pure-u-xxl-10-24,.pure-u-xxl-11-24,.pure-u-xxl-12-24,.pure-u-xxl-13-24,.pure-u-xxl-14-24,.pure-u-xxl-15-24,.pure-u-xxl-16-24,.pure-u-xxl-17-24,.pure-u-xxl-18-24,.pure-u-xxl-19-24,.pure-u-xxl-20-24,.pure-u-xxl-21-24,.pure-u-xxl-22-24,.pure-u-xxl-23-24,.pure-u-xxl-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-xxl-1-24{width:4.1667%}.pure-u-xxl-1-12,.pure-u-xxl-2-24{width:8.3333%}.pure-u-xxl-1-8,.pure-u-xxl-3-24{width:12.5%}.pure-u-xxl-1-6,.pure-u-xxl-4-24{width:16.6667%}.pure-u-xxl-1-5{width:20%}.pure-u-xxl-5-24{width:20.8333%}.pure-u-xxl-1-4,.pure-u-xxl-6-24{width:25%}.pure-u-xxl-7-24{width:29.1667%}.pure-u-xxl-1-3,.pure-u-xxl-8-24{width:33.3333%}.pure-u-xxl-3-8,.pure-u-xxl-9-24{width:37.5%}.pure-u-xxl-2-5{width:40%}.pure-u-xxl-5-12,.pure-u-xxl-10-24{width:41.6667%}.pure-u-xxl-11-24{width:45.8333%}.pure-u-xxl-1-2,.pure-u-xxl-12-24{width:50%}.pure-u-xxl-13-24{width:54.1667%}.pure-u-xxl-7-12,.pure-u-xxl-14-24{width:58.3333%}.pure-u-xxl-3-5{width:60%}.pure-u-xxl-5-8,.pure-u-xxl-15-24{width:62.5%}.pure-u-xxl-2-3,.pure-u-xxl-16-24{width:66.6667%}.pure-u-xxl-17-24{width:70.8333%}.pure-u-xxl-3-4,.pure-u-xxl-18-24{width:75%}.pure-u-xxl-19-24{width:79.1667%}.pure-u-xxl-4-5{width:80%}.pure-u-xxl-5-6,.pure-u-xxl-20-24{width:83.3333%}.pure-u-xxl-7-8,.pure-u-xxl-21-24{width:87.5%}.pure-u-xxl-11-12,.pure-u-xxl-22-24{width:91.6667%}.pure-u-xxl-23-24{width:95.8333%}.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-5-5,.pure-u-xxl-24-24{width:100%}}body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}header,main,footer{margin:2em}.padding{padding:1em}.overflow{overflow-x:auto}.overflow table{width:100%}.overflow table td,.overflow table th{padding:.5em}.overflow table td:not(:last-child),.overflow table th:not(:last-child){width:1px;text-align:left;white-space:nowrap}.overflow table td:last-child,.overflow table th:last-child{white-space:nowrap}.hspace{height:1em;display:block}.pure-button-action{background-color:#42b8dd!important}.pure-button-success{background-color:#1cb841!important}.pure-button-xsmall{font-size:70%}.pure-button-small{font-size:85%}.pure-button-large{font-size:110%}.pure-button-xlarge{font-size:125%}.error-message{color:red;background-color:pink;border:1px solid red;padding:2px}
\ No newline at end of file
+html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}main{display:block}h1{margin:.67em 0;font-size:2em}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace;font-size:1em}a{background-color:#0000}abbr[title]{text-decoration:underline;border-bottom:none;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace;font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:100%;line-height:1.15}button,input{overflow:visible}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}button::-moz-focus-inner,[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;max-width:100%;white-space:normal;padding:0;display:table}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template,[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;flex-flow:wrap;align-content:flex-start;font-family:FreeSans,Arimo,Droid Sans,Helvetica,Arial,sans-serif;display:flex}@media (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-.43em}.pure-u{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-2,.pure-u-1-3,.pure-u-2-3,.pure-u-1-4,.pure-u-3-4,.pure-u-1-5,.pure-u-2-5,.pure-u-3-5,.pure-u-4-5,.pure-u-5-5,.pure-u-1-6,.pure-u-5-6,.pure-u-1-8,.pure-u-3-8,.pure-u-5-8,.pure-u-7-8,.pure-u-1-12,.pure-u-5-12,.pure-u-7-12,.pure-u-11-12,.pure-u-1-24,.pure-u-2-24,.pure-u-3-24,.pure-u-4-24,.pure-u-5-24,.pure-u-6-24,.pure-u-7-24,.pure-u-8-24,.pure-u-9-24,.pure-u-10-24,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-5-12,.pure-u-10-24{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-7-12,.pure-u-14-24{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-5-8,.pure-u-15-24{width:62.5%}.pure-u-2-3,.pure-u-16-24{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-3-4,.pure-u-18-24{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-5-6,.pure-u-20-24{width:83.3333%}.pure-u-7-8,.pure-u-21-24{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-5-5,.pure-u-24-24{width:100%}.pure-button{white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;user-select:none;box-sizing:border-box;line-height:normal;display:inline-block}.pure-button::-moz-focus-inner{border:0;padding:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{color:#000c;background-color:#e6e6e6;border:#0000;border-radius:2px;padding:.5em 1em;font-family:inherit;font-size:100%;text-decoration:none}.pure-button-hover,.pure-button:hover,.pure-button:focus{background-image:linear-gradient(#0000,#0000000d 40%,#0000001a)}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{border-color:#000;box-shadow:inset 0 0 0 1px #00000026,inset 0 0 6px #0003}.pure-button[disabled],.pure-button-disabled,.pure-button-disabled:hover,.pure-button-disabled:focus,.pure-button-disabled:active{opacity:.4;cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;pointer-events:none;background-image:none;border:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{color:#fff;background-color:#0078e7}.pure-button-group .pure-button{border-right:1px solid #0003;border-radius:0;margin:0}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-right:none;border-top-right-radius:2px;border-bottom-right-radius:2px}.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form select,.pure-form textarea{vertical-align:middle;box-sizing:border-box;border:1px solid #ccc;border-radius:4px;padding:.5em .6em;display:inline-block;box-shadow:inset 0 1px 3px #ddd}.pure-form input:not([type]){box-sizing:border-box;border:1px solid #ccc;border-radius:4px;padding:.5em .6em;display:inline-block;box-shadow:inset 0 1px 3px #ddd}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=text]:focus,.pure-form input[type=password]:focus,.pure-form input[type=email]:focus,.pure-form input[type=url]:focus,.pure-form input[type=date]:focus,.pure-form input[type=month]:focus,.pure-form input[type=time]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=week]:focus,.pure-form input[type=number]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=color]:focus,.pure-form select:focus,.pure-form textarea:focus,.pure-form input:not([type]):focus{border-color:#129fea;outline:0}.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus,.pure-form input[type=checkbox]:focus{outline:1px auto #129fea}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=text][disabled],.pure-form input[type=password][disabled],.pure-form input[type=email][disabled],.pure-form input[type=url][disabled],.pure-form input[type=date][disabled],.pure-form input[type=month][disabled],.pure-form input[type=time][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=week][disabled],.pure-form input[type=number][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=color][disabled],.pure-form select[disabled],.pure-form textarea[disabled],.pure-form input:not([type])[disabled]{cursor:not-allowed;color:#cad2d3;background-color:#eaeded}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{color:#777;background-color:#eee;border-color:#ccc}.pure-form input:focus:invalid,.pure-form textarea:focus:invalid,.pure-form select:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus,.pure-form input[type=checkbox]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;background-color:#fff;border:1px solid #ccc}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{border:0;margin:0;padding:.35em 0 .75em}.pure-form legend{width:100%;color:#333;border-bottom:1px solid #e5e5e5;margin-bottom:.3em;padding:.3em 0;display:block}.pure-form-stacked input[type=text],.pure-form-stacked input[type=password],.pure-form-stacked input[type=email],.pure-form-stacked input[type=url],.pure-form-stacked input[type=date],.pure-form-stacked input[type=month],.pure-form-stacked input[type=time],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=week],.pure-form-stacked input[type=number],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=color],.pure-form-stacked input[type=file],.pure-form-stacked select,.pure-form-stacked label,.pure-form-stacked textarea,.pure-form-stacked input:not([type]){margin:.25em 0;display:block}.pure-form-aligned input,.pure-form-aligned textarea,.pure-form-aligned select,.pure-form-message-inline{vertical-align:middle;display:inline-block}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;vertical-align:middle;width:10em;margin:0 1em 0 0;display:inline-block}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form input.pure-input-rounded,.pure-form .pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{border-radius:0;margin:0 0 -1px;padding:10px;display:block;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{border-radius:4px 4px 0 0;margin:0;top:1px}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{border-radius:4px;margin:0;top:1px}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{border-radius:0 0 4px 4px;margin:0;top:-2px}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{color:#666;vertical-align:middle;padding-left:.3em;font-size:.875em;display:inline-block}.pure-form-message{color:#666;font-size:.875em;display:block}@media only screen and (max-width:480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=text],.pure-form input[type=password],.pure-form input[type=email],.pure-form input[type=url],.pure-form input[type=date],.pure-form input[type=month],.pure-form input[type=time],.pure-form input[type=datetime],.pure-form input[type=datetime-local],.pure-form input[type=week],.pure-form input[type=number],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=color],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=text],.pure-group input[type=password],.pure-group input[type=email],.pure-group input[type=url],.pure-group input[type=date],.pure-group input[type=month],.pure-group input[type=time],.pure-group input[type=datetime],.pure-group input[type=datetime-local],.pure-group input[type=week],.pure-group input[type=number],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=color]{margin-bottom:0}.pure-form-aligned .pure-control-group label{text-align:left;width:100%;margin-bottom:.3em;display:block}.pure-form-aligned .pure-controls{margin:1.5em 0 0}.pure-form-message-inline,.pure-form-message{padding:.2em 0 .8em;font-size:.75em;display:block}}.pure-menu{box-sizing:border-box}.pure-menu-fixed{z-index:3;position:fixed;top:0;left:0}.pure-menu-list,.pure-menu-item{position:relative}.pure-menu-list{margin:0;padding:0;list-style:none}.pure-menu-item{height:100%;margin:0;padding:0}.pure-menu-link,.pure-menu-heading{white-space:nowrap;text-decoration:none;display:block}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-separator{vertical-align:middle;display:inline-block}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{z-index:3;margin:0;padding:0;display:none;position:absolute;top:0;left:100%}.pure-menu-horizontal .pure-menu-children{width:inherit;top:auto;left:0}.pure-menu-allow-hover:hover>.pure-menu-children,.pure-menu-active>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{content:"▸";padding-left:.5em;font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"▾"}.pure-menu-scrollable{overflow-x:hidden;overflow-y:scroll}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;padding:.5em 0;overflow-x:auto;overflow-y:hidden}.pure-menu-separator,.pure-menu-horizontal .pure-menu-children .pure-menu-separator{height:1px;background-color:#ccc;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{width:auto;display:block}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-link,.pure-menu-heading{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{cursor:default;background-color:#0000}.pure-menu-active>.pure-menu-link,.pure-menu-link:hover,.pure-menu-link:focus{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;text-align:center;padding:1em 0;font:italic 85%/1 arial,sans-serif}.pure-table td,.pure-table th{font-size:inherit;border-width:0 0 0 1px;border-left-style:solid;border-left-color:#cbcbcb;margin:0;padding:.5em 1em;overflow:visible}.pure-table thead{color:#000;text-align:left;vertical-align:bottom;background-color:#e0e0e0}.pure-table td{background-color:#0000}.pure-table-odd td,.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px;border-bottom-style:solid;border-bottom-color:#cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0}@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-2,.pure-u-sm-1-3,.pure-u-sm-2-3,.pure-u-sm-1-4,.pure-u-sm-3-4,.pure-u-sm-1-5,.pure-u-sm-2-5,.pure-u-sm-3-5,.pure-u-sm-4-5,.pure-u-sm-5-5,.pure-u-sm-1-6,.pure-u-sm-5-6,.pure-u-sm-1-8,.pure-u-sm-3-8,.pure-u-sm-5-8,.pure-u-sm-7-8,.pure-u-sm-1-12,.pure-u-sm-5-12,.pure-u-sm-7-12,.pure-u-sm-11-12,.pure-u-sm-1-24,.pure-u-sm-2-24,.pure-u-sm-3-24,.pure-u-sm-4-24,.pure-u-sm-5-24,.pure-u-sm-6-24,.pure-u-sm-7-24,.pure-u-sm-8-24,.pure-u-sm-9-24,.pure-u-sm-10-24,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-5-12,.pure-u-sm-10-24{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-7-12,.pure-u-sm-14-24{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-5-8,.pure-u-sm-15-24{width:62.5%}.pure-u-sm-2-3,.pure-u-sm-16-24{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-3-4,.pure-u-sm-18-24{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-5-6,.pure-u-sm-20-24{width:83.3333%}.pure-u-sm-7-8,.pure-u-sm-21-24{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-5-5,.pure-u-sm-24-24{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-2,.pure-u-md-1-3,.pure-u-md-2-3,.pure-u-md-1-4,.pure-u-md-3-4,.pure-u-md-1-5,.pure-u-md-2-5,.pure-u-md-3-5,.pure-u-md-4-5,.pure-u-md-5-5,.pure-u-md-1-6,.pure-u-md-5-6,.pure-u-md-1-8,.pure-u-md-3-8,.pure-u-md-5-8,.pure-u-md-7-8,.pure-u-md-1-12,.pure-u-md-5-12,.pure-u-md-7-12,.pure-u-md-11-12,.pure-u-md-1-24,.pure-u-md-2-24,.pure-u-md-3-24,.pure-u-md-4-24,.pure-u-md-5-24,.pure-u-md-6-24,.pure-u-md-7-24,.pure-u-md-8-24,.pure-u-md-9-24,.pure-u-md-10-24,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-5-12,.pure-u-md-10-24{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-7-12,.pure-u-md-14-24{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-5-8,.pure-u-md-15-24{width:62.5%}.pure-u-md-2-3,.pure-u-md-16-24{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-3-4,.pure-u-md-18-24{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-5-6,.pure-u-md-20-24{width:83.3333%}.pure-u-md-7-8,.pure-u-md-21-24{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-5-5,.pure-u-md-24-24{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-2,.pure-u-lg-1-3,.pure-u-lg-2-3,.pure-u-lg-1-4,.pure-u-lg-3-4,.pure-u-lg-1-5,.pure-u-lg-2-5,.pure-u-lg-3-5,.pure-u-lg-4-5,.pure-u-lg-5-5,.pure-u-lg-1-6,.pure-u-lg-5-6,.pure-u-lg-1-8,.pure-u-lg-3-8,.pure-u-lg-5-8,.pure-u-lg-7-8,.pure-u-lg-1-12,.pure-u-lg-5-12,.pure-u-lg-7-12,.pure-u-lg-11-12,.pure-u-lg-1-24,.pure-u-lg-2-24,.pure-u-lg-3-24,.pure-u-lg-4-24,.pure-u-lg-5-24,.pure-u-lg-6-24,.pure-u-lg-7-24,.pure-u-lg-8-24,.pure-u-lg-9-24,.pure-u-lg-10-24,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-5-12,.pure-u-lg-10-24{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-7-12,.pure-u-lg-14-24{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-5-8,.pure-u-lg-15-24{width:62.5%}.pure-u-lg-2-3,.pure-u-lg-16-24{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-3-4,.pure-u-lg-18-24{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-5-6,.pure-u-lg-20-24{width:83.3333%}.pure-u-lg-7-8,.pure-u-lg-21-24{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-5-5,.pure-u-lg-24-24{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-2,.pure-u-xl-1-3,.pure-u-xl-2-3,.pure-u-xl-1-4,.pure-u-xl-3-4,.pure-u-xl-1-5,.pure-u-xl-2-5,.pure-u-xl-3-5,.pure-u-xl-4-5,.pure-u-xl-5-5,.pure-u-xl-1-6,.pure-u-xl-5-6,.pure-u-xl-1-8,.pure-u-xl-3-8,.pure-u-xl-5-8,.pure-u-xl-7-8,.pure-u-xl-1-12,.pure-u-xl-5-12,.pure-u-xl-7-12,.pure-u-xl-11-12,.pure-u-xl-1-24,.pure-u-xl-2-24,.pure-u-xl-3-24,.pure-u-xl-4-24,.pure-u-xl-5-24,.pure-u-xl-6-24,.pure-u-xl-7-24,.pure-u-xl-8-24,.pure-u-xl-9-24,.pure-u-xl-10-24,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-xl-1-24{width:4.1667%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%}.pure-u-xl-1-5{width:20%}.pure-u-xl-5-24{width:20.8333%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%}.pure-u-xl-7-24{width:29.1667%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%}.pure-u-xl-2-5{width:40%}.pure-u-xl-5-12,.pure-u-xl-10-24{width:41.6667%}.pure-u-xl-11-24{width:45.8333%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%}.pure-u-xl-13-24{width:54.1667%}.pure-u-xl-7-12,.pure-u-xl-14-24{width:58.3333%}.pure-u-xl-3-5{width:60%}.pure-u-xl-5-8,.pure-u-xl-15-24{width:62.5%}.pure-u-xl-2-3,.pure-u-xl-16-24{width:66.6667%}.pure-u-xl-17-24{width:70.8333%}.pure-u-xl-3-4,.pure-u-xl-18-24{width:75%}.pure-u-xl-19-24{width:79.1667%}.pure-u-xl-4-5{width:80%}.pure-u-xl-5-6,.pure-u-xl-20-24{width:83.3333%}.pure-u-xl-7-8,.pure-u-xl-21-24{width:87.5%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%}.pure-u-xl-23-24{width:95.8333%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-5-5,.pure-u-xl-24-24{width:100%}}@media screen and (min-width:120em){.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-1-2,.pure-u-xxl-1-3,.pure-u-xxl-2-3,.pure-u-xxl-1-4,.pure-u-xxl-3-4,.pure-u-xxl-1-5,.pure-u-xxl-2-5,.pure-u-xxl-3-5,.pure-u-xxl-4-5,.pure-u-xxl-5-5,.pure-u-xxl-1-6,.pure-u-xxl-5-6,.pure-u-xxl-1-8,.pure-u-xxl-3-8,.pure-u-xxl-5-8,.pure-u-xxl-7-8,.pure-u-xxl-1-12,.pure-u-xxl-5-12,.pure-u-xxl-7-12,.pure-u-xxl-11-12,.pure-u-xxl-1-24,.pure-u-xxl-2-24,.pure-u-xxl-3-24,.pure-u-xxl-4-24,.pure-u-xxl-5-24,.pure-u-xxl-6-24,.pure-u-xxl-7-24,.pure-u-xxl-8-24,.pure-u-xxl-9-24,.pure-u-xxl-10-24,.pure-u-xxl-11-24,.pure-u-xxl-12-24,.pure-u-xxl-13-24,.pure-u-xxl-14-24,.pure-u-xxl-15-24,.pure-u-xxl-16-24,.pure-u-xxl-17-24,.pure-u-xxl-18-24,.pure-u-xxl-19-24,.pure-u-xxl-20-24,.pure-u-xxl-21-24,.pure-u-xxl-22-24,.pure-u-xxl-23-24,.pure-u-xxl-24-24{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto;display:inline-block}.pure-u-xxl-1-24{width:4.1667%}.pure-u-xxl-1-12,.pure-u-xxl-2-24{width:8.3333%}.pure-u-xxl-1-8,.pure-u-xxl-3-24{width:12.5%}.pure-u-xxl-1-6,.pure-u-xxl-4-24{width:16.6667%}.pure-u-xxl-1-5{width:20%}.pure-u-xxl-5-24{width:20.8333%}.pure-u-xxl-1-4,.pure-u-xxl-6-24{width:25%}.pure-u-xxl-7-24{width:29.1667%}.pure-u-xxl-1-3,.pure-u-xxl-8-24{width:33.3333%}.pure-u-xxl-3-8,.pure-u-xxl-9-24{width:37.5%}.pure-u-xxl-2-5{width:40%}.pure-u-xxl-5-12,.pure-u-xxl-10-24{width:41.6667%}.pure-u-xxl-11-24{width:45.8333%}.pure-u-xxl-1-2,.pure-u-xxl-12-24{width:50%}.pure-u-xxl-13-24{width:54.1667%}.pure-u-xxl-7-12,.pure-u-xxl-14-24{width:58.3333%}.pure-u-xxl-3-5{width:60%}.pure-u-xxl-5-8,.pure-u-xxl-15-24{width:62.5%}.pure-u-xxl-2-3,.pure-u-xxl-16-24{width:66.6667%}.pure-u-xxl-17-24{width:70.8333%}.pure-u-xxl-3-4,.pure-u-xxl-18-24{width:75%}.pure-u-xxl-19-24{width:79.1667%}.pure-u-xxl-4-5{width:80%}.pure-u-xxl-5-6,.pure-u-xxl-20-24{width:83.3333%}.pure-u-xxl-7-8,.pure-u-xxl-21-24{width:87.5%}.pure-u-xxl-11-12,.pure-u-xxl-22-24{width:91.6667%}.pure-u-xxl-23-24{width:95.8333%}.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-5-5,.pure-u-xxl-24-24{width:100%}}body{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol}header,main,footer{margin:2em}.padding{padding:1em}.overflow{overflow-x:auto}.overflow table{width:100%}.overflow table td,.overflow table th{padding:.5em}.overflow table td:not(:last-child),.overflow table th:not(:last-child){width:1px;text-align:left;white-space:nowrap}.overflow table td:last-child,.overflow table th:last-child{white-space:nowrap}.hspace{height:1em;display:block}.pure-form-group{display:inline}.pure-button-action{background-color:#42b8dd!important}.pure-button-success{background-color:#1cb841!important}.pure-button-danger{background:#ca3c3c!important}.pure-button-warning{background:#df7514!important}.pure-button-xsmall{font-size:70%}.pure-button-small{font-size:85%}.pure-button-large{font-size:110%}.pure-button-xlarge{font-size:125%}.error-message{color:red;background-color:pink;border:1px solid red;padding:2px}
\ No newline at end of file
diff --git a/internal/dis/component/control/static/src/base/index.css b/internal/dis/component/control/static/src/base/index.css
index f5660ce..73361a0 100644
--- a/internal/dis/component/control/static/src/base/index.css
+++ b/internal/dis/component/control/static/src/base/index.css
@@ -21,7 +21,7 @@ footer {
}
.overflow table td,
-.overflow table th{
+.overflow table th {
padding: .5em .5em;
}
@@ -41,14 +41,26 @@ footer {
display: block;
height: 1em;
}
+.pure-form-group {
+ display: inline;
+}
.pure-button-action {
background-color: rgb(66, 184, 221) !important;
}
+
.pure-button-success {
background-color: rgb(28, 184, 65) !important;
}
+.pure-button-danger {
+ background: rgb(202, 60, 60) !important;
+}
+
+.pure-button-warning {
+ background: rgb(223, 117, 20) !important;
+}
+
.pure-button-xsmall {
font-size: 70%;
}
@@ -70,4 +82,4 @@ footer {
border: 1px solid red;
padding: 2px;
color: red;
-}
+}
\ No newline at end of file
diff --git a/internal/models/models.go b/internal/models/models.go
new file mode 100644
index 0000000..8d2fe58
--- /dev/null
+++ b/internal/models/models.go
@@ -0,0 +1,2 @@
+// Package contains all database models
+package models
diff --git a/internal/models/user.go b/internal/models/user.go
index 7973fd5..8c80215 100644
--- a/internal/models/user.go
+++ b/internal/models/user.go
@@ -10,9 +10,33 @@ type User struct {
User string `gorm:"column:user;not null;unique"` // name of the user
PasswordHash []byte `gorm:"column:password"` // password of the user, hashed
- TOTPEnabled bool `gorm:"column:totpenabled"` // is totp enabled for the user
+ TOTPEnabled *bool `gorm:"column:totpenabled"` // is totp enabled for the user
TOTPURL string `gorm:"column:totp"` // the totp of the user
- Enabled bool `gorm:"enabled;not null"`
- Admin bool `gorm:"column:admin;not null"`
+ Enabled *bool `gorm:"enabled;not null"`
+ Admin *bool `gorm:"column:admin;not null"`
+}
+
+func (user *User) IsAdmin() bool {
+ return user.Admin != nil && *user.Admin
+}
+
+func (user *User) SetAdmin(v bool) {
+ user.Admin = &v
+}
+
+func (user *User) IsEnabled() bool {
+ return user.Enabled != nil && *user.Enabled
+}
+
+func (user *User) SetEnabled(v bool) {
+ user.Enabled = &v
+}
+
+func (user *User) IsTOTPEnabled() bool {
+ return user.TOTPEnabled != nil && *user.TOTPEnabled
+}
+
+func (user *User) SetTOTPEnabled(v bool) {
+ user.TOTPEnabled = &v
}
diff --git a/internal/wisski/ingredient/bookkeeping/bookkeeping.go b/internal/wisski/ingredient/bookkeeping/bookkeeping.go
index 2cb30b3..3e380b8 100644
--- a/internal/wisski/ingredient/bookkeeping/bookkeeping.go
+++ b/internal/wisski/ingredient/bookkeeping/bookkeeping.go
@@ -25,7 +25,7 @@ func (bk *Bookkeeping) Save(ctx context.Context) error {
}
// Update based on the primary key!
- return sdb.Where("pk = ?", bk.Instance.Pk).Updates(&bk.Instance).Error
+ return sdb.Select("*").Save(&bk.Instance).Error
}
// Delete deletes this instance from the bookkeeping table
diff --git a/pkg/httpx/errors.go b/pkg/httpx/errors.go
index 9c3e33c..61c7048 100644
--- a/pkg/httpx/errors.go
+++ b/pkg/httpx/errors.go
@@ -49,6 +49,7 @@ func StatusInterceptor(contentType string, body func(code int, text string) ([]b
return ErrInterceptor{
Errors: map[error]Response{
+ ErrBadRequest: makeResponse(http.StatusBadRequest),
ErrNotFound: makeResponse(http.StatusNotFound),
ErrForbidden: makeResponse(http.StatusForbidden),
ErrMethodNotAllowed: makeResponse(http.StatusMethodNotAllowed),
@@ -59,6 +60,7 @@ func StatusInterceptor(contentType string, body func(code int, text string) ([]b
// Common errors accepted by all httpx handlers
var (
+ ErrBadRequest = errors.New("httpx: Bad Request")
ErrNotFound = errors.New("httpx: Not Found")
ErrForbidden = errors.New("httpx: Forbidden")
ErrMethodNotAllowed = errors.New("httpx: Method Not Allowed")
diff --git a/pkg/httpx/form.go b/pkg/httpx/form.go
index c6269b9..a90ff4a 100644
--- a/pkg/httpx/form.go
+++ b/pkg/httpx/form.go
@@ -224,4 +224,5 @@ type InputType string
const (
TextField InputType = "text"
PasswordField InputType = "password"
+ CheckboxField InputType = "checkbox"
)