templates: Add a proper menu and navigation
This commit is contained in:
parent
0bb7f99fa3
commit
a00195be16
76 changed files with 336 additions and 233 deletions
File diff suppressed because one or more lines are too long
|
|
@ -22,6 +22,7 @@ func (dis *Distillery) init() {
|
||||||
lazy.RegisterPoolGroup[component.Cronable](&dis.pool)
|
lazy.RegisterPoolGroup[component.Cronable](&dis.pool)
|
||||||
lazy.RegisterPoolGroup[component.UserDeleteHook](&dis.pool)
|
lazy.RegisterPoolGroup[component.UserDeleteHook](&dis.pool)
|
||||||
lazy.RegisterPoolGroup[component.Table](&dis.pool)
|
lazy.RegisterPoolGroup[component.Table](&dis.pool)
|
||||||
|
lazy.RegisterPoolGroup[component.Menuable](&dis.pool)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ type Auth struct {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ component.Routeable = (*Auth)(nil)
|
_ component.Routeable = (*Auth)(nil)
|
||||||
|
_ component.Menuable = (*Auth)(nil)
|
||||||
_ component.Table = (*Auth)(nil)
|
_ component.Table = (*Auth)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ func (panel *UserPanel) Routes() component.Routes {
|
||||||
Prefix: "/user/",
|
Prefix: "/user/",
|
||||||
CSRF: true,
|
CSRF: true,
|
||||||
Decorator: panel.Dependencies.Auth.Require(nil),
|
Decorator: panel.Dependencies.Auth.Require(nil),
|
||||||
|
|
||||||
|
MenuPriority: component.MenuUser,
|
||||||
|
MenuTitle: "User",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,13 +84,20 @@ type userFormContext struct {
|
||||||
User *models.User
|
User *models.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) UserFormContext(ctx httpx.FormContext, r *http.Request) any {
|
func (panel *UserPanel) UserFormContext(last component.MenuItem) func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "User", Path: "/user/"},
|
||||||
|
last,
|
||||||
|
}
|
||||||
|
return func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
||||||
uctx := userFormContext{FormContext: ctx}
|
uctx := userFormContext{FormContext: ctx}
|
||||||
panel.Dependencies.Custom.Update(&uctx, r)
|
panel.Dependencies.Custom.Update(&uctx, r, crumbs)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
uctx.User = &user.User
|
uctx.User = &user.User
|
||||||
}
|
}
|
||||||
return uctx
|
return uctx
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
|
||||||
|
|
@ -37,7 +38,7 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: passwordTemplate,
|
RenderTemplate: passwordTemplate,
|
||||||
RenderTemplateContext: panel.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Change Password", Path: "/user/password/"}),
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"]
|
old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,3 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "_form.html" . }}
|
||||||
{{ define "form/title" }}Change Password{{ end }}
|
{{ define "form/title" }}Change Password{{ end }}
|
||||||
{{ define "form/button" }}Update{{ end }}
|
{{ define "form/button" }}Update{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/user/">{{ .User.User }}</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/user/password/">Change Password</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,6 @@
|
||||||
{{ define "form/title" }}Disable TOTP{{ end }}
|
{{ define "form/title" }}Disable TOTP{{ end }}
|
||||||
{{ define "form/button" }}Disable{{ end }}
|
{{ define "form/button" }}Disable{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/user/">{{ .User.User }}</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/disable/">Disable TOTP</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "_form.html" . }}
|
||||||
{{ define "form/title" }}Enable TOTP{{ end }}
|
{{ define "form/title" }}Enable TOTP{{ end }}
|
||||||
{{ define "form/button" }}Enable{{ end }}
|
{{ define "form/button" }}Enable{{ end }}
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/user/">{{ .User.User }}</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/enable/">Enable TOTP</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "_form.html" . }}
|
||||||
{{ define "form/title" }}Enable TOTP{{ end }}
|
{{ define "form/title" }}Enable TOTP{{ end }}
|
||||||
{{ define "form/button" }}Enable{{ end }}
|
{{ define "form/button" }}Enable{{ end }}
|
||||||
{{ define "header" }}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/user/">{{ .User.User }}</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/enroll/">Enroll TOTP</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
<a href="{{ .TOTPURL }}">
|
<a href="{{ .TOTPURL }}">
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}User{{ end }}
|
{{ define "title" }}User{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-primary" href="/user/">{{ .User.User }}</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"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/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
@ -32,7 +33,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
|
|
||||||
RenderTemplate: totpEnableTemplate,
|
RenderTemplate: totpEnableTemplate,
|
||||||
RenderTemplateContext: panel.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"}),
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
password := values["password"]
|
password := values["password"]
|
||||||
|
|
@ -77,7 +78,10 @@ type totpEnrollContext struct {
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
totpEnrollTemplate := panel.Dependencies.Custom.Template(totpEnrollTemplate)
|
totpEnrollTemplate := panel.Dependencies.Custom.Template(totpEnrollTemplate)
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "User", Path: "/user/"},
|
||||||
|
{Title: "Enable TOTP", Path: "/user/totp/enable/"},
|
||||||
|
}
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
|
{Name: "password", Type: field.Password, Autocomplete: field.CurrentPassword, EmptyOnError: true, Label: "Current Password"},
|
||||||
|
|
@ -99,7 +103,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
FormContext: context,
|
FormContext: context,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
panel.Dependencies.Custom.Update(&ctx.userFormContext, r)
|
panel.Dependencies.Custom.Update(&ctx.userFormContext, r, crumbs)
|
||||||
|
|
||||||
if err == nil && user != nil {
|
if err == nil && user != nil {
|
||||||
ctx.userFormContext.User = &user.User
|
ctx.userFormContext.User = &user.User
|
||||||
|
|
@ -164,7 +168,7 @@ func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
||||||
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
RenderTemplate: totpDisableTemplate,
|
RenderTemplate: totpDisableTemplate,
|
||||||
RenderTemplateContext: panel.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Disable TOTP", Path: "/user/totp/disable/"}),
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
password, otp := values["password"], values["otp"]
|
password, otp := values["password"], values["otp"]
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"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/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
|
|
@ -35,9 +36,13 @@ type GrantWithURL struct {
|
||||||
|
|
||||||
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
userTemplate := panel.Dependencies.Custom.Template(userTemplate)
|
userTemplate := panel.Dependencies.Custom.Template(userTemplate)
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "User", Path: "/user/"},
|
||||||
|
}
|
||||||
|
|
||||||
return &httpx.HTMLHandler[routeUserContext]{
|
return &httpx.HTMLHandler[routeUserContext]{
|
||||||
Handler: func(r *http.Request) (ruc routeUserContext, err error) {
|
Handler: func(r *http.Request) (ruc routeUserContext, err error) {
|
||||||
panel.Dependencies.Custom.Update(&ruc, r)
|
panel.Dependencies.Custom.Update(&ruc, r, crumbs)
|
||||||
|
|
||||||
// find the user
|
// find the user
|
||||||
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,17 @@ func (auth *Auth) Require(perm Permission) func(http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Has checks if the given request has the given permission.
|
||||||
|
// If an error occurs, returns false.
|
||||||
|
func (auth *Auth) Has(perm Permission, r *http.Request) bool {
|
||||||
|
user, err := auth.UserOf(r)
|
||||||
|
if err != nil || user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ok, err := perm.Permit(user, r)
|
||||||
|
return err == nil && ok.Granted()
|
||||||
|
}
|
||||||
|
|
||||||
// Admin represents a permission that checks if a user is an administrator and has totp enabled.
|
// Admin represents a permission that checks if a user is an administrator and has totp enabled.
|
||||||
var Admin Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
var Admin Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
||||||
return Bool2Grant(user != nil && user.IsAdmin() && user.IsTOTPEnabled(), "user needs to have admin permissions and passcode enabled"), nil
|
return Bool2Grant(user != nil && user.IsAdmin() && user.IsTOTPEnabled(), "user needs to have admin permissions and passcode enabled"), nil
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package auth
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
@ -67,6 +69,22 @@ func (auth *Auth) session(r *http.Request) (*sessions.Session, error) {
|
||||||
}).Get(r, control.SessionCookie)
|
}).Get(r, control.SessionCookie)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (auth *Auth) Menu(r *http.Request) []component.MenuItem {
|
||||||
|
|
||||||
|
user, err := auth.UserOf(r)
|
||||||
|
if user == nil || err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []component.MenuItem{
|
||||||
|
{
|
||||||
|
Title: "Logout",
|
||||||
|
Path: "/auth/logout",
|
||||||
|
Priority: component.MenuAuth,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type contextUserKey struct{}
|
type contextUserKey struct{}
|
||||||
|
|
||||||
var ctxUserKey = contextUserKey{}
|
var ctxUserKey = contextUserKey{}
|
||||||
|
|
@ -126,7 +144,9 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
if context.Err != nil {
|
if context.Err != nil {
|
||||||
context.Err = errLoginFailed
|
context.Err = errLoginFailed
|
||||||
}
|
}
|
||||||
httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context, r), nil, loginTemplate, "", w, r)
|
httpx.WriteHTML(auth.Dependencies.Custom.NewForm(context, r, []component.MenuItem{
|
||||||
|
{Title: "Login", Path: template.URL(r.URL.RequestURI())},
|
||||||
|
}), nil, loginTemplate, "", w, r)
|
||||||
},
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ type Admin struct {
|
||||||
var (
|
var (
|
||||||
_ component.DistilleryFetcher = (*Admin)(nil)
|
_ component.DistilleryFetcher = (*Admin)(nil)
|
||||||
_ component.Routeable = (*Admin)(nil)
|
_ component.Routeable = (*Admin)(nil)
|
||||||
|
_ component.Menuable = (*Admin)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (admin *Admin) Routes() component.Routes {
|
func (admin *Admin) Routes() component.Routes {
|
||||||
|
|
@ -50,6 +51,19 @@ func (admin *Admin) Routes() component.Routes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (admin *Admin) Menu(r *http.Request) []component.MenuItem {
|
||||||
|
if !admin.Dependencies.Auth.Has(auth.Admin, r) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []component.MenuItem{
|
||||||
|
{
|
||||||
|
Title: "Admin",
|
||||||
|
Path: "/admin/",
|
||||||
|
Priority: component.MenuAdmin,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http.Handler, err error) {
|
func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http.Handler, err error) {
|
||||||
|
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
@ -62,17 +76,17 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle everything
|
|
||||||
router.HandlerFunc(http.MethodGet, route, func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
http.Redirect(w, r, route+"index", http.StatusTemporaryRedirect)
|
|
||||||
})
|
|
||||||
|
|
||||||
// add a handler for the index page
|
// add a handler for the index page
|
||||||
router.Handler(http.MethodGet, route+"index", httpx.HTMLHandler[indexContext]{
|
router.Handler(http.MethodGet, route, httpx.HTMLHandler[indexContext]{
|
||||||
Handler: admin.index,
|
Handler: admin.index,
|
||||||
Template: admin.Dependencies.Custom.Template(indexTemplate),
|
Template: admin.Dependencies.Custom.Template(indexTemplate),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// fallback to the "/" page
|
||||||
|
router.HandlerFunc(http.MethodGet, route+"index", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Redirect(w, r, route, http.StatusTemporaryRedirect)
|
||||||
|
})
|
||||||
|
|
||||||
// add a handler for the user page
|
// add a handler for the user page
|
||||||
router.Handler(http.MethodGet, route+"users", httpx.HTMLHandler[userContext]{
|
router.Handler(http.MethodGet, route+"users", httpx.HTMLHandler[userContext]{
|
||||||
Handler: admin.users,
|
Handler: admin.users,
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
|
|
@ -28,7 +30,10 @@ type componentContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
|
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
|
||||||
admin.Dependencies.Custom.Update(&cp, r)
|
admin.Dependencies.Custom.Update(&cp, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Components", Path: "/admin/components/"},
|
||||||
|
})
|
||||||
|
|
||||||
cp.Analytics = *admin.Analytics
|
cp.Analytics = *admin.Analytics
|
||||||
return
|
return
|
||||||
|
|
@ -49,10 +54,15 @@ type ingredientsContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
|
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
|
||||||
admin.Dependencies.Custom.Update(&cp, r)
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
|
|
||||||
|
admin.Dependencies.Custom.Update(&cp, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
||||||
|
{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")},
|
||||||
|
})
|
||||||
|
|
||||||
// find the instance itself!
|
// find the instance itself!
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
|
||||||
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
||||||
if err == instances.ErrWissKINotFound {
|
if err == instances.ErrWissKINotFound {
|
||||||
return cp, httpx.ErrNotFound
|
return cp, httpx.ErrNotFound
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package admin
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
|
|
@ -38,7 +40,11 @@ type grantsContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) {
|
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) {
|
||||||
admin.Dependencies.Custom.Update(gc, r)
|
admin.Dependencies.Custom.Update(gc, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
||||||
|
{Title: "Grants", Path: template.URL("/admin/instance/" + slug + "/grants/")},
|
||||||
|
})
|
||||||
|
|
||||||
// find the instance itself
|
// find the instance itself
|
||||||
gc.instance, err = admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
gc.instance, err = admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin - Components Page{{ end }}
|
{{ define "title" }}Distillery Admin - Components Page{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/components">Components</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ template "_anal.html" .Analytics }}
|
{{ template "_anal.html" .Analytics }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }} - Grants{{ end }}
|
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }} - Grants{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button" href="/admin/instance/{{ .Instance.Slug }}">Instance</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/grants/{{ .Instance.Slug }}">Grants</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ $csrf := .CSRF }}
|
{{ $csrf := .CSRF }}
|
||||||
{{ $slug := .Instance.Slug }}
|
{{ $slug := .Instance.Slug }}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin{{ end }}
|
{{ define "title" }}Distillery Admin{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header" }}
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/index">Admin</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<div class="pure-button-group" role="group" aria-label="Actions">
|
<div class="pure-button-group" role="group" aria-label="Actions">
|
||||||
<a class="pure-button" href="/admin/users">Users</a>
|
<a class="pure-button" href="/admin/users">Users</a>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }} - Ingredients{{ end }}
|
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }} - Ingredients{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button" href="/admin/instance/{{ .Instance.Slug }}">Instance</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/ingredients/{{ .Instance.Slug }}">Ingredients</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ template "_anal.html" .Analytics }}
|
{{ template "_anal.html" .Analytics }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }}{{ end }}
|
{{ define "title" }}Distillery Admin - {{ .Instance.Slug }}{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header" }}
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/instance/{{ .Instance.Slug }}">Instance</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<div class="pure-button-group" role="group" aria-label="Actions">
|
<div class="pure-button-group" role="group" aria-label="Actions">
|
||||||
<a class="pure-button" href="/admin/grants/{{ .Info.Slug }}">Grants</a>
|
<a class="pure-button" href="/admin/grants/{{ .Info.Slug }}">Grants</a>
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,3 @@
|
||||||
{{ define "form/title" }}Distillery Admin - Create User{{ end }}
|
{{ define "form/title" }}Distillery Admin - Create User{{ end }}
|
||||||
{{ define "form/button" }}Create{{ end }}
|
{{ define "form/button" }}Create{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button" href="/admin/users">Users</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/users/create">Create</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,7 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Distillery Admin - Users{{ end }}
|
{{ define "title" }}Distillery Admin - Users{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
{{ define "header" }}
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/admin/index">Admin</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/admin/users">Users</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
<div class="pure-button-group" role="group" aria-label="Actions">
|
<div class="pure-button-group" role="group" aria-label="Actions">
|
||||||
<a class="pure-button" href="/admin/users/create">Create New</a>
|
<a class="pure-button" href="/admin/users/create">Create New</a>
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,9 @@ type indexContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
|
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
|
||||||
admin.Dependencies.Custom.Update(&idx, r)
|
admin.Dependencies.Custom.Update(&idx, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
})
|
||||||
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
|
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
|
|
@ -28,10 +30,14 @@ type instanceContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
|
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
|
||||||
admin.Dependencies.Custom.Update(&is, r)
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
|
|
||||||
|
admin.Dependencies.Custom.Update(&is, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
||||||
|
})
|
||||||
|
|
||||||
// find the instance itself!
|
// find the instance itself!
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
|
||||||
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
|
||||||
if err == instances.ErrWissKINotFound {
|
if err == instances.ErrWissKINotFound {
|
||||||
return is, httpx.ErrNotFound
|
return is, httpx.ErrNotFound
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"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/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
|
|
@ -31,7 +32,10 @@ type userContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
||||||
admin.Dependencies.Custom.Update(&uc, r)
|
admin.Dependencies.Custom.Update(&uc, r, []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Users", Path: "/admin/users/"},
|
||||||
|
})
|
||||||
|
|
||||||
uc.Error = r.URL.Query().Get("error")
|
uc.Error = r.URL.Query().Get("error")
|
||||||
uc.Users, err = admin.Dependencies.Auth.Users(r.Context())
|
uc.Users, err = admin.Dependencies.Auth.Users(r.Context())
|
||||||
|
|
@ -58,6 +62,11 @@ type createUserResult struct {
|
||||||
|
|
||||||
func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
||||||
userCreateTemplate := admin.Dependencies.Custom.Template(userCreateTemplate)
|
userCreateTemplate := admin.Dependencies.Custom.Template(userCreateTemplate)
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "Admin", Path: "/admin/"},
|
||||||
|
{Title: "Users", Path: "/admin/users"},
|
||||||
|
{Title: "Create", Path: "/admin/users/create"},
|
||||||
|
}
|
||||||
|
|
||||||
return &httpx.Form[createUserResult]{
|
return &httpx.Form[createUserResult]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -68,7 +77,9 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: userCreateTemplate,
|
RenderTemplate: userCreateTemplate,
|
||||||
RenderTemplateContext: admin.Dependencies.Custom.RenderContext,
|
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
return admin.Dependencies.Custom.NewForm(ctx, r, crumbs)
|
||||||
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (cu createUserResult, err error) {
|
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"] == field.CheckboxChecked
|
cu.User, cu.Passsword, cu.Admin = values["username"], values["password"], values["admin"] == field.CheckboxChecked
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,9 @@ func (*Home) Routes() component.Routes {
|
||||||
Prefix: "/",
|
Prefix: "/",
|
||||||
MatchAllDomains: true,
|
MatchAllDomains: true,
|
||||||
CSRF: false,
|
CSRF: false,
|
||||||
|
|
||||||
|
MenuTitle: "WissKI Distillery",
|
||||||
|
MenuPriority: component.MenuHome,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
|
|
@ -14,7 +15,7 @@ import (
|
||||||
|
|
||||||
//go:embed "public.html"
|
//go:embed "public.html"
|
||||||
var publicHTMLStr string
|
var publicHTMLStr string
|
||||||
var publicTemplate = static.AssetsHome.MustParseShared("public.html", publicHTMLStr)
|
var publicTemplate = static.AssetsDefault.MustParseShared("public.html", publicHTMLStr)
|
||||||
|
|
||||||
type publicContext struct {
|
type publicContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -24,6 +25,9 @@ type publicContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "WissKI Distillery", Path: "/"},
|
||||||
|
}
|
||||||
return httpx.HTMLHandler[publicContext]{
|
return httpx.HTMLHandler[publicContext]{
|
||||||
Handler: func(r *http.Request) (pc publicContext, err error) {
|
Handler: func(r *http.Request) (pc publicContext, err error) {
|
||||||
// only act on the root path!
|
// only act on the root path!
|
||||||
|
|
@ -31,7 +35,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
return pc, httpx.ErrNotFound
|
return pc, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
home.Dependencies.Custom.Update(&pc, r)
|
home.Dependencies.Custom.Update(&pc, r, crumbs)
|
||||||
|
|
||||||
pc.Instances = home.homeInstances.Get(nil)
|
pc.Instances = home.homeInstances.Get(nil)
|
||||||
pc.SelfRedirect = home.Config.SelfRedirect.String()
|
pc.SelfRedirect = home.Config.SelfRedirect.String()
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}WissKI Distillery{{ end }}
|
{{ define "title" }}WissKI Distillery{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button pure-button-primary" href="/">Home</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/news/">News</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ block "@custom/about" . }}
|
{{ block "@custom/about" . }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ var (
|
||||||
|
|
||||||
//go:embed "legal.html"
|
//go:embed "legal.html"
|
||||||
var legalTemplateString string
|
var legalTemplateString string
|
||||||
var legalTemplate = static.AssetsLegal.MustParseShared("legal.html", legalTemplateString)
|
var legalTemplate = static.AssetsDefault.MustParseShared("legal.html", legalTemplateString)
|
||||||
|
|
||||||
func (legal *Legal) Routes() component.Routes {
|
func (legal *Legal) Routes() component.Routes {
|
||||||
return component.Routes{
|
return component.Routes{
|
||||||
|
|
@ -59,7 +59,9 @@ type legalContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
|
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
|
||||||
legal.Dependencies.Custom.Update(&lc, r)
|
legal.Dependencies.Custom.Update(&lc, r, []component.MenuItem{
|
||||||
|
{Title: "Legal", Path: "/legal/"},
|
||||||
|
})
|
||||||
|
|
||||||
lc.LegalNotices = cli.LegalNotices
|
lc.LegalNotices = cli.LegalNotices
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}Legal{{ end }}
|
{{ define "title" }}Legal{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<!-- no header -->
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<h2 id="cookies">Cookie Usage</h2>
|
<h2 id="cookies">Cookie Usage</h2>
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,9 @@ func (*News) Routes() component.Routes {
|
||||||
Prefix: "/news/",
|
Prefix: "/news/",
|
||||||
Exact: true,
|
Exact: true,
|
||||||
CSRF: false,
|
CSRF: false,
|
||||||
|
|
||||||
|
MenuTitle: "News",
|
||||||
|
MenuPriority: component.MenuNews,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -111,7 +114,7 @@ func Items() ([]Item, error) {
|
||||||
|
|
||||||
//go:embed "news.html"
|
//go:embed "news.html"
|
||||||
var newsHTMLStr string
|
var newsHTMLStr string
|
||||||
var newsTemplate = static.AssetsHome.MustParseShared("news.html", newsHTMLStr)
|
var newsTemplate = static.AssetsDefault.MustParseShared("news.html", newsHTMLStr)
|
||||||
|
|
||||||
type newsContext struct {
|
type newsContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -120,6 +123,10 @@ type newsContext struct {
|
||||||
|
|
||||||
// HandleRoute returns the handler for the requested path
|
// HandleRoute returns the handler for the requested path
|
||||||
func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "News", Path: "/news/"},
|
||||||
|
}
|
||||||
|
|
||||||
items, itemsErr := Items()
|
items, itemsErr := Items()
|
||||||
if itemsErr != nil {
|
if itemsErr != nil {
|
||||||
zerolog.Ctx(ctx).Err(itemsErr).Msg("Unable to load news items")
|
zerolog.Ctx(ctx).Err(itemsErr).Msg("Unable to load news items")
|
||||||
|
|
@ -127,7 +134,7 @@ func (news *News) HandleRoute(ctx context.Context, path string) (http.Handler, e
|
||||||
|
|
||||||
return httpx.HTMLHandler[newsContext]{
|
return httpx.HTMLHandler[newsContext]{
|
||||||
Handler: func(r *http.Request) (nc newsContext, err error) {
|
Handler: func(r *http.Request) (nc newsContext, err error) {
|
||||||
news.Dependencies.Custom.Update(&nc, r)
|
news.Dependencies.Custom.Update(&nc, r, crumbs)
|
||||||
nc.Items, err = items, itemsErr
|
nc.Items, err = items, itemsErr
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}News{{ end }}
|
{{ define "title" }}News{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<p>
|
|
||||||
<a class="pure-button" href="/">Home</a> >
|
|
||||||
<a class="pure-button pure-button-primary" href="/news/">News</a>
|
|
||||||
</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ type Assets struct {
|
||||||
Styles string // <link> tags inserted by the asset
|
Styles string // <link> tags inserted by the asset
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate node build.mjs Home User Admin Legal
|
//go:generate node build.mjs Default User Admin
|
||||||
|
|
||||||
// MustParse parses a new template from the given source
|
// MustParse parses a new template from the given source
|
||||||
// and calls [RegisterAssoc] on it.
|
// and calls [RegisterAssoc] on it.
|
||||||
|
|
|
||||||
|
|
@ -7,26 +7,20 @@ import _ "embed"
|
||||||
//go:embed "assets_disclaimer.txt"
|
//go:embed "assets_disclaimer.txt"
|
||||||
var AssetsDisclaimer string
|
var AssetsDisclaimer string
|
||||||
|
|
||||||
// AssetsHome contains assets for the 'Home' entrypoint.
|
// AssetsDefault contains assets for the 'Default' entrypoint.
|
||||||
var AssetsHome = Assets{
|
var AssetsDefault = Assets{
|
||||||
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script>`,
|
Scripts: `<script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script>`,
|
||||||
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Home.f9675eae.css">`,
|
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/Default.f9675eae.css">`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetsUser contains assets for the 'User' entrypoint.
|
// AssetsUser contains assets for the 'User' entrypoint.
|
||||||
var AssetsUser = Assets{
|
var AssetsUser = Assets{
|
||||||
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/User.4197014b.js"></script><script src="/static/User.30d54198.js" nomodule="" defer></script>`,
|
Scripts: `<script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/User.4197014b.js"></script><script src="/static/User.30d54198.js" nomodule="" defer></script>`,
|
||||||
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/User.38d394c2.css">`,
|
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/User.38d394c2.css">`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
// AssetsAdmin contains assets for the 'Admin' entrypoint.
|
||||||
var AssetsAdmin = Assets{
|
var AssetsAdmin = Assets{
|
||||||
Scripts: `<script nomodule="" defer src="/static/User.30d54198.js"></script><script type="module" src="/static/User.4197014b.js"></script><script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Admin.4ca3cb6f.js"></script><script src="/static/Admin.9750ba9c.js" nomodule="" defer></script>`,
|
Scripts: `<script nomodule="" defer src="/static/User.30d54198.js"></script><script type="module" src="/static/User.4197014b.js"></script><script type="module" src="/static/Default.38d394c2.js"></script><script src="/static/Default.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Admin.4ca3cb6f.js"></script><script src="/static/Admin.9750ba9c.js" nomodule="" defer></script>`,
|
||||||
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.38d394c2.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
|
Styles: `<link rel="stylesheet" href="/static/Default.db26a303.css"><link rel="stylesheet" href="/static/Admin.6d59e220.css"><link rel="stylesheet" href="/static/User.38d394c2.css"><link rel="stylesheet" href="/static/Admin.6d2ae968.css">`,
|
||||||
}
|
|
||||||
|
|
||||||
// AssetsLegal contains assets for the 'Legal' entrypoint.
|
|
||||||
var AssetsLegal = Assets{
|
|
||||||
Scripts: `<script type="module" src="/static/Home.38d394c2.js"></script><script src="/static/Home.38d394c2.js" nomodule="" defer></script><script type="module" src="/static/Legal.38d394c2.js"></script><script src="/static/Legal.38d394c2.js" nomodule="" defer></script>`,
|
|
||||||
Styles: `<link rel="stylesheet" href="/static/Home.4b303448.css"><link rel="stylesheet" href="/static/Legal.d1531eba.css"><link rel="stylesheet" href="/static/Legal.20259812.css">`,
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
"github.com/tkw1536/goprogram/lib/reflectx"
|
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// baseContextName is the name of the [BaseContext] type
|
// baseContextName is the name of the [BaseContext] type
|
||||||
|
|
@ -25,6 +26,10 @@ type BaseContext struct {
|
||||||
|
|
||||||
GeneratedAt time.Time // time this page was generated at
|
GeneratedAt time.Time // time this page was generated at
|
||||||
|
|
||||||
|
// Menu and breadcrumbs
|
||||||
|
Menu []component.MenuItem
|
||||||
|
Crumbs []component.MenuItem
|
||||||
|
|
||||||
CSRF template.HTML // CSRF Field
|
CSRF template.HTML // CSRF Field
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +48,8 @@ const (
|
||||||
// The given request *must not* be nil.
|
// The given request *must not* be nil.
|
||||||
//
|
//
|
||||||
// For convenience the passed context is also returned.
|
// For convenience the passed context is also returned.
|
||||||
func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
|
func (tc *BaseContext) use(custom *Custom, r *http.Request, crumbs []component.MenuItem) *BaseContext {
|
||||||
|
// tc.custom = custom
|
||||||
tc.inited = true
|
tc.inited = true
|
||||||
tc.requestWasNil = r == nil
|
tc.requestWasNil = r == nil
|
||||||
|
|
||||||
|
|
@ -55,9 +61,20 @@ func (tc *BaseContext) use(base component.Base, r *http.Request) *BaseContext {
|
||||||
tc.CSRF = csrf.TemplateField(r)
|
tc.CSRF = csrf.TemplateField(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// build the menu
|
||||||
|
tc.Menu = custom.BuildMenu(r)
|
||||||
|
|
||||||
|
// build the breadcrumbs
|
||||||
|
tc.Crumbs = slices.Clone(crumbs)
|
||||||
|
last := len(tc.Crumbs) - 1
|
||||||
|
for i := range tc.Crumbs {
|
||||||
|
tc.Crumbs[i].Active = i == last
|
||||||
|
}
|
||||||
|
|
||||||
return tc
|
return tc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DoInitCheck is called by the template to check that the BaseContext was initialized properly
|
||||||
func (bc BaseContext) DoInitCheck() template.HTML {
|
func (bc BaseContext) DoInitCheck() template.HTML {
|
||||||
if !bc.inited {
|
if !bc.inited {
|
||||||
return initError
|
return initError
|
||||||
|
|
@ -69,26 +86,21 @@ func (bc BaseContext) DoInitCheck() template.HTML {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewForm is like New, but returns a new BaseFormContext
|
// NewForm is like New, but returns a new BaseFormContext
|
||||||
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request) (ctx BaseFormContext) {
|
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, crumbs []component.MenuItem) (ctx BaseFormContext) {
|
||||||
ctx.FormContext = context
|
ctx.FormContext = context
|
||||||
ctx.use(custom.Base, r)
|
ctx.use(custom, r, crumbs)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenderContext is exactly like NewForm, but returns any to be used as httpx.Form.RenderTemplateContext
|
|
||||||
func (custom *Custom) RenderContext(ctx httpx.FormContext, r *http.Request) any {
|
|
||||||
return custom.NewForm(ctx, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates an embedded BaseContext field in context.
|
// Update updates an embedded BaseContext field in context.
|
||||||
//
|
//
|
||||||
// Assumes that context is a pointer to a struct type.
|
// Assumes that context is a pointer to a struct type.
|
||||||
// If this is not the case, might call panic().
|
// If this is not the case, might call panic().
|
||||||
func (custom *Custom) Update(context any, r *http.Request) *BaseContext {
|
func (custom *Custom) Update(context any, r *http.Request, crumbs []component.MenuItem) *BaseContext {
|
||||||
ctx := reflect.ValueOf(context).
|
ctx := reflect.ValueOf(context).
|
||||||
Elem().FieldByName(baseContextName).Addr().
|
Elem().FieldByName(baseContextName).Addr().
|
||||||
Interface().(*BaseContext)
|
Interface().(*BaseContext)
|
||||||
ctx.use(custom.Base, r)
|
ctx.use(custom, r, crumbs)
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,20 @@ package custom
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Custom implements theme and page customization.
|
// Custom implements theme and page customization.
|
||||||
type Custom struct {
|
type Custom struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
// nothing yet
|
Routeables []component.Routeable
|
||||||
|
Menuable []component.Menuable
|
||||||
}
|
}
|
||||||
|
menu lazy.Lazy[[]component.MenuItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_ component.Backupable = (*Custom)(nil)
|
_ component.Backupable = (*Custom)(nil)
|
||||||
|
_ component.Menuable = (*Custom)(nil)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
47
internal/dis/component/control/static/custom/menu.go
Normal file
47
internal/dis/component/control/static/custom/menu.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package custom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/mux"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getMenuItems gets a fresh copy of the cached slice of menu items
|
||||||
|
func (custom *Custom) Menu(r *http.Request) []component.MenuItem {
|
||||||
|
return custom.menu.Get(func() []component.MenuItem {
|
||||||
|
items := make([]component.MenuItem, 0, len(custom.Dependencies.Routeables))
|
||||||
|
for _, route := range custom.Dependencies.Routeables {
|
||||||
|
routes := route.Routes()
|
||||||
|
if routes.MenuTitle == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, component.MenuItem{
|
||||||
|
Title: routes.MenuTitle,
|
||||||
|
Priority: routes.MenuPriority,
|
||||||
|
Path: template.URL(routes.Prefix),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
slices.SortFunc(items, component.MenuItemSort)
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (custom *Custom) BuildMenu(r *http.Request) []component.MenuItem {
|
||||||
|
// NOTE(twiesing): Don't name this method "Menu", as it will cause
|
||||||
|
// a stack overflow.
|
||||||
|
path := mux.NormalizePath(r.URL.Path)
|
||||||
|
|
||||||
|
// get the static menu items, and then return all the regular ones
|
||||||
|
var items []component.MenuItem
|
||||||
|
for _, m := range custom.Dependencies.Menuable {
|
||||||
|
items = append(items, m.Menu(r)...)
|
||||||
|
}
|
||||||
|
for i, item := range items {
|
||||||
|
items[i].Active = string(item.Path) == path
|
||||||
|
}
|
||||||
|
slices.SortFunc(items, component.MenuItemSort)
|
||||||
|
return items
|
||||||
|
}
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1 +0,0 @@
|
||||||
html{background-color:#87a485}body{max-width:80vw!important}
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -8,6 +8,30 @@ footer {
|
||||||
margin: 2em;
|
margin: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.pure-menu, nav.breadcrumbs {
|
||||||
|
padding-top: 1em;
|
||||||
|
padding-bottom: 1em;
|
||||||
|
border-bottom: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs {
|
||||||
|
padding-left: 1em;
|
||||||
|
font-size: small;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs a:not(:last-child)::after {
|
||||||
|
cursor: default;
|
||||||
|
content: " > ";
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
nav.breadcrumbs a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: blue !important;
|
||||||
|
}
|
||||||
|
nav.breadcrumbs a.active {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
footer {
|
footer {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
border-top: 1px solid black;
|
border-top: 1px solid black;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
html {
|
|
||||||
background-color: #87A485;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
max-width: 80vw !important;
|
|
||||||
}
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
import "latex.css/style.min.css"
|
|
||||||
|
|
@ -10,6 +10,21 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{ .BaseContext.DoInitCheck }}
|
{{ .BaseContext.DoInitCheck }}
|
||||||
|
<nav class="pure-menu pure-menu-horizontal">
|
||||||
|
<ul class="pure-menu-list">
|
||||||
|
{{ range .BaseContext.Menu }}
|
||||||
|
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}">
|
||||||
|
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<nav class="breadcrumbs" role="group" aria-label="Breadcrumbs">
|
||||||
|
{{ range .BaseContext.Crumbs }}
|
||||||
|
<a class="{{ if .Active }}active{{ end }}" href="{{ .Path }}">{{ .Title }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</nav>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1 id="top">{{ template "title" . }}</h1>
|
<h1 id="top">{{ template "title" . }}</h1>
|
||||||
{{ block "header" . }}<!-- no header by default -->{{ end }}
|
{{ block "header" . }}<!-- no header by default -->{{ end }}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
|
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
|
||||||
|
|
||||||
{{ define "header" }}<!-- no header -->{{ end }}
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
||||||
|
|
|
||||||
36
internal/dis/component/menu.go
Normal file
36
internal/dis/component/menu.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package component
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Menuable is a component that provides a menu
|
||||||
|
type Menuable interface {
|
||||||
|
Component
|
||||||
|
|
||||||
|
Menu(r *http.Request) []MenuItem
|
||||||
|
}
|
||||||
|
type MenuItem struct {
|
||||||
|
Title string
|
||||||
|
Path template.URL
|
||||||
|
Active bool
|
||||||
|
|
||||||
|
Priority MenuPriority // menu priority
|
||||||
|
}
|
||||||
|
|
||||||
|
func MenuItemSort(a, b MenuItem) bool {
|
||||||
|
return a.Priority < b.Priority
|
||||||
|
}
|
||||||
|
|
||||||
|
type MenuPriority int
|
||||||
|
|
||||||
|
// Menu* indicates priorities of the menu
|
||||||
|
const (
|
||||||
|
MenuHome MenuPriority = iota
|
||||||
|
MenuNews
|
||||||
|
MenuResolver
|
||||||
|
MenuUser
|
||||||
|
MenuAdmin
|
||||||
|
MenuAuth
|
||||||
|
)
|
||||||
|
|
@ -41,12 +41,15 @@ func (resolver *Resolver) Routes() component.Routes {
|
||||||
Prefix: "/wisski/get/",
|
Prefix: "/wisski/get/",
|
||||||
Aliases: []string{"/go/"},
|
Aliases: []string{"/go/"},
|
||||||
CSRF: false,
|
CSRF: false,
|
||||||
|
|
||||||
|
MenuTitle: "Resolver",
|
||||||
|
MenuPriority: component.MenuResolver,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "resolver.html"
|
//go:embed "resolver.html"
|
||||||
var resolverHTMLStr string
|
var resolverHTMLStr string
|
||||||
var resolverTemplate = static.AssetsHome.MustParseShared("resolver.html", resolverHTMLStr)
|
var resolverTemplate = static.AssetsDefault.MustParseShared("resolver.html", resolverHTMLStr)
|
||||||
|
|
||||||
type resolverContext struct {
|
type resolverContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -55,6 +58,9 @@ type resolverContext struct {
|
||||||
|
|
||||||
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
resolverTemplate := resolver.Dependencies.Custom.Template(resolverTemplate)
|
resolverTemplate := resolver.Dependencies.Custom.Template(resolverTemplate)
|
||||||
|
crumbs := []component.MenuItem{
|
||||||
|
{Title: "Resolver", Path: "/wisski/get/"},
|
||||||
|
}
|
||||||
|
|
||||||
logger := zerolog.Ctx(ctx)
|
logger := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
|
|
@ -65,7 +71,7 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
|
||||||
ctx := resolverContext{
|
ctx := resolverContext{
|
||||||
IndexContext: context,
|
IndexContext: context,
|
||||||
}
|
}
|
||||||
resolver.Dependencies.Custom.Update(&ctx, r)
|
resolver.Dependencies.Custom.Update(&ctx, r, crumbs)
|
||||||
|
|
||||||
httpx.WriteHTML(ctx, nil, resolverTemplate, "", w, r)
|
httpx.WriteHTML(ctx, nil, resolverTemplate, "", w, r)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
{{ template "_base.html" . }}
|
{{ template "_base.html" . }}
|
||||||
{{ define "title" }}WissKI Resolver{{ end }}
|
{{ define "title" }}WissKI Resolver{{ end }}
|
||||||
|
|
||||||
{{ define "header"}}
|
|
||||||
<!-- no header -->
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<h1>wdresolve</h1>
|
<h1>wdresolve</h1>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ type Routes struct {
|
||||||
// MatchAllDomains indicates that all domains, even the non-default domain, should be matched
|
// MatchAllDomains indicates that all domains, even the non-default domain, should be matched
|
||||||
MatchAllDomains bool
|
MatchAllDomains bool
|
||||||
|
|
||||||
|
// MenuTitle and MenuPriority return the priority and title of this menu item
|
||||||
|
MenuTitle string
|
||||||
|
MenuPriority MenuPriority
|
||||||
|
|
||||||
// Exact indicates that only the exact prefix, as opposed to any sub-paths, are matched.
|
// Exact indicates that only the exact prefix, as opposed to any sub-paths, are matched.
|
||||||
// Trailing '/'s are automatically trimmed, even with an exact match.
|
// Trailing '/'s are automatically trimmed, even with an exact match.
|
||||||
Exact bool
|
Exact bool
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ func (mux *Mux[T]) Add(path string, predicate Predicate, exact bool, h http.Hand
|
||||||
mux.prefixes = make(map[string][]handler)
|
mux.prefixes = make(map[string][]handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
mPath := normalizePath(path)
|
mPath := NormalizePath(path)
|
||||||
mHandler := handler{Predicate: predicate, Handler: h}
|
mHandler := handler{Predicate: predicate, Handler: h}
|
||||||
if exact {
|
if exact {
|
||||||
mux.exacts[mPath] = append(mux.exacts[mPath], mHandler)
|
mux.exacts[mPath] = append(mux.exacts[mPath], mHandler)
|
||||||
|
|
@ -71,7 +71,7 @@ func (mux *Mux[T]) Match(r *http.Request, prepare bool) (http.Handler, bool) {
|
||||||
r = mux.Prepare(r)
|
r = mux.Prepare(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
candidate := normalizePath(r.URL.Path)
|
candidate := NormalizePath(r.URL.Path)
|
||||||
|
|
||||||
// match the exact path first
|
// match the exact path first
|
||||||
for _, h := range mux.exacts[candidate] {
|
for _, h := range mux.exacts[candidate] {
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"path"
|
"path"
|
||||||
)
|
)
|
||||||
|
|
||||||
// normalizePath normalizes the provided path.
|
// NormalizePath normalizes the provided path.
|
||||||
// It ensures that there is both a leading and trailing slash.
|
// It ensures that there is both a leading and trailing slash.
|
||||||
func normalizePath(value string) string {
|
func NormalizePath(value string) string {
|
||||||
value = path.Clean(value)
|
value = path.Clean(value)
|
||||||
if value != "/" {
|
if value != "/" {
|
||||||
value = value + "/"
|
value = value + "/"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue