custom: Improve templating of assets
This commit is contained in:
parent
7d0fb60d67
commit
b6bf0a8900
19 changed files with 516 additions and 432 deletions
4
Makefile
4
Makefile
|
|
@ -6,7 +6,9 @@ wdcli:
|
||||||
go generate ./internal/dis/component/control/static/
|
go generate ./internal/dis/component/control/static/
|
||||||
go build -o ./wdcli ./cmd/wdcli
|
go build -o ./wdcli ./cmd/wdcli
|
||||||
|
|
||||||
deps:
|
deps: internal/dis/component/control/static/node_modules
|
||||||
|
|
||||||
|
internal/dis/component/control/static/node_modules:
|
||||||
cd internal/dis/component/control/static/ && yarn install
|
cd internal/dis/component/control/static/ && yarn install
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
|
|
|
||||||
|
|
@ -112,20 +112,24 @@ type userFormContext struct {
|
||||||
User *models.User
|
User *models.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) UserFormContext(last component.MenuItem, gaps custom.BaseContextGaps) func(ctx httpx.FormContext, r *http.Request) any {
|
func (panel *UserPanel) UserFormContext2(tpl *custom.Template[userFormContext], last component.MenuItem, gaps ...custom.BaseContextGaps) func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
gaps.Crumbs = []component.MenuItem{
|
var g custom.BaseContextGaps
|
||||||
|
if len(gaps) > 1 {
|
||||||
|
panic("UserFormContext2: gaps must be of length 0 or 1")
|
||||||
|
}
|
||||||
|
if len(gaps) == 1 {
|
||||||
|
g = gaps[0]
|
||||||
|
}
|
||||||
|
g.Crumbs = []component.MenuItem{
|
||||||
{Title: "User", Path: "/user/"},
|
{Title: "User", Path: "/user/"},
|
||||||
last,
|
last,
|
||||||
}
|
}
|
||||||
return func(ctx httpx.FormContext, r *http.Request) any {
|
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
|
||||||
|
|
||||||
|
return custom.MappedHandler(tpl, func(ctx httpx.FormContext, r *http.Request) (userFormContext, custom.BaseContextGaps) {
|
||||||
uctx := userFormContext{FormContext: ctx}
|
uctx := userFormContext{FormContext: ctx}
|
||||||
panel.Dependencies.Custom.Update(&uctx, r, gaps)
|
if user, err := panel.Dependencies.Auth.UserOf(r); err == nil {
|
||||||
if err == nil {
|
|
||||||
uctx.User = &user.User
|
uctx.User = &user.User
|
||||||
}
|
}
|
||||||
return uctx
|
return uctx, g
|
||||||
}
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/password.html"
|
//go:embed "templates/password.html"
|
||||||
var passwordHTMLString string
|
var passwordHTML []byte
|
||||||
var passwordTemplate = static.AssetsUser.MustParseShared("password.html", passwordHTMLString)
|
var passwordTemplate = custom.Parse[userFormContext]("password.html", passwordHTML, static.AssetsUser)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errPasswordsNotIdentical = errors.New("passwords are not identical")
|
errPasswordsNotIdentical = errors.New("passwords are not identical")
|
||||||
|
|
@ -28,7 +28,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
||||||
passwordTemplate := panel.Dependencies.Custom.Template(passwordTemplate)
|
tpl := passwordTemplate.Prepare(panel.Dependencies.Custom)
|
||||||
|
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -39,8 +39,8 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: passwordTemplate,
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Change Password", Path: "/user/password/"}, custom.BaseContextGaps{}),
|
RenderTemplateContext: panel.UserFormContext2(tpl, 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"]
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/ssh.html"
|
//go:embed "templates/ssh.html"
|
||||||
var sshTemplateStr string
|
var sshHTML []byte
|
||||||
var sshTemplate = static.AssetsUser.MustParseShared("ssh.html", sshTemplateStr)
|
var sshTemplate = custom.Parse[SSHTemplateContext]("ssh.html", sshHTML, static.AssetsUser)
|
||||||
|
|
||||||
type SSHTemplateContext struct {
|
type SSHTemplateContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -37,8 +37,7 @@ type SSHTemplateContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
||||||
sshTemplate := panel.Dependencies.Custom.Template(sshTemplate)
|
tpl := sshTemplate.Prepare(panel.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "User", Path: "/user/"},
|
{Title: "User", Path: "/user/"},
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
|
|
@ -46,12 +45,9 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
||||||
Actions: []component.MenuItem{
|
Actions: []component.MenuItem{
|
||||||
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
return httpx.HTMLHandler[SSHTemplateContext]{
|
|
||||||
Handler: func(r *http.Request) (sc SSHTemplateContext, err error) {
|
|
||||||
panel.Dependencies.Custom.Update(&sc, r, gaps)
|
|
||||||
|
|
||||||
|
return tpl.HTMLHandler(func(r *http.Request) (sc SSHTemplateContext, err error) {
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return sc, err
|
return sc, err
|
||||||
|
|
@ -75,19 +71,7 @@ func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
return sc, nil
|
return sc, nil
|
||||||
},
|
})
|
||||||
Template: sshTemplate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed "templates/ssh_add.html"
|
|
||||||
var sshAddTemplateStr string
|
|
||||||
var sshAddTemplate = static.AssetsUser.MustParseShared("ssh_add.html", sshAddTemplateStr)
|
|
||||||
|
|
||||||
type addKeyResult struct {
|
|
||||||
User *auth.AuthUser
|
|
||||||
Comment string
|
|
||||||
Key ssh.PublicKey
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -128,15 +112,24 @@ func (panel *UserPanel) sshDeleteRoute(ctx context.Context) http.Handler {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed "templates/ssh_add.html"
|
||||||
|
var sshAddHTML []byte
|
||||||
|
var sshAddTemplate = custom.ParseForm("ssh_add.html", sshAddHTML, static.AssetsUser)
|
||||||
|
|
||||||
|
type addKeyResult struct {
|
||||||
|
User *auth.AuthUser
|
||||||
|
Comment string
|
||||||
|
Key ssh.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
||||||
sshAddTemplate := panel.Dependencies.Custom.Template(sshAddTemplate)
|
tpl := sshAddTemplate.Prepare(panel.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "User", Path: "/user/"},
|
{Title: "User", Path: "/user/"},
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
return &httpx.Form[addKeyResult]{
|
return &httpx.Form[addKeyResult]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -145,10 +138,8 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: sshAddTemplate,
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
|
RenderTemplateContext: custom.FormTemplateContext(tpl),
|
||||||
return panel.Dependencies.Custom.NewForm(ctx, r, gaps)
|
|
||||||
},
|
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (ak addKeyResult, err error) {
|
Validate: func(r *http.Request, values map[string]string) (ak addKeyResult, err error) {
|
||||||
ak.User, err = panel.Dependencies.Auth.UserOf(r)
|
ak.User, err = panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/totp_enable.html"
|
//go:embed "templates/totp_enable.html"
|
||||||
var totpEnableStr string
|
var totpEnableHTML []byte
|
||||||
var totpEnableTemplate = static.AssetsUser.MustParseShared("totp_enable.html", totpEnableStr)
|
var totpEnable = custom.Parse[userFormContext]("totp_enable.html", totpEnableHTML, static.AssetsUser)
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
totpEnableTemplate := panel.Dependencies.Custom.Template(totpEnableTemplate)
|
tpl := totpEnable.Prepare(panel.Dependencies.Custom)
|
||||||
|
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -33,8 +33,8 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
|
|
||||||
RenderTemplate: totpEnableTemplate,
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"}, custom.BaseContextGaps{}),
|
RenderTemplateContext: panel.UserFormContext2(tpl, 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"]
|
||||||
|
|
@ -68,8 +68,8 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "templates/totp_enroll.html"
|
//go:embed "templates/totp_enroll.html"
|
||||||
var totpEnrollStr string
|
var totpEnrollHTML []byte
|
||||||
var totpEnrollTemplate = static.AssetsUser.MustParseShared("totp_enroll.html", totpEnrollStr)
|
var totpEnrollTemplate = custom.Parse[totpEnrollContext]("totp_enroll.html", totpEnrollHTML, static.AssetsUser)
|
||||||
|
|
||||||
type totpEnrollContext struct {
|
type totpEnrollContext struct {
|
||||||
userFormContext
|
userFormContext
|
||||||
|
|
@ -80,13 +80,13 @@ 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)
|
tpl := totpEnrollTemplate.Prepare(panel.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "User", Path: "/user/"},
|
{Title: "User", Path: "/user/"},
|
||||||
{Title: "Enable TOTP", Path: "/user/totp/enable/"},
|
{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"},
|
||||||
|
|
@ -108,7 +108,6 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
FormContext: context,
|
FormContext: context,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
panel.Dependencies.Custom.Update(&ctx.userFormContext, r, gaps)
|
|
||||||
|
|
||||||
if err == nil && user != nil {
|
if err == nil && user != nil {
|
||||||
ctx.userFormContext.User = &user.User
|
ctx.userFormContext.User = &user.User
|
||||||
|
|
@ -121,7 +120,7 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
ctx.TOTPURL = template.URL(secret.URL())
|
ctx.TOTPURL = template.URL(secret.URL())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
httpx.WriteHTML(ctx, nil, totpEnrollTemplate, "", w, r)
|
tpl.Execute(w, r, ctx)
|
||||||
},
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
|
|
@ -156,11 +155,11 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "templates/totp_disable.html"
|
//go:embed "templates/totp_disable.html"
|
||||||
var totpDisableStr string
|
var totpDisableHTML []byte
|
||||||
var totpDisableTemplate = static.AssetsUser.MustParseShared("totp_disable.html", totpDisableStr)
|
var totpDisableTemplate = custom.Parse[userFormContext]("totp_disable.html", totpDisableHTML, static.AssetsUser)
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
||||||
totpDisableTemplate := panel.Dependencies.Custom.Template(totpDisableTemplate)
|
tpl := totpDisableTemplate.Prepare(panel.Dependencies.Custom)
|
||||||
|
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -173,8 +172,8 @@ func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
RenderTemplate: totpDisableTemplate,
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext(component.MenuItem{Title: "Disable TOTP", Path: "/user/totp/disable/"}, custom.BaseContextGaps{}),
|
RenderTemplateContext: panel.UserFormContext2(tpl, 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"]
|
||||||
|
|
|
||||||
|
|
@ -12,17 +12,13 @@ import (
|
||||||
"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/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/user.html"
|
//go:embed "templates/user.html"
|
||||||
var userHTMLStr string
|
var userHTML []byte
|
||||||
var userTemplate = static.AssetsUser.MustParseShared(
|
var userTemplate = custom.Parse[userContext]("user.html", userHTML, static.AssetsUser)
|
||||||
"user.html",
|
|
||||||
userHTMLStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
type routeUserContext struct {
|
type userContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
*auth.AuthUser
|
*auth.AuthUser
|
||||||
|
|
||||||
|
|
@ -35,8 +31,7 @@ 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)
|
tpl := userTemplate.Prepare(panel.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "User", Path: "/user/"},
|
{Title: "User", Path: "/user/"},
|
||||||
},
|
},
|
||||||
|
|
@ -45,19 +40,17 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
{Title: "*to be replaced*", Path: ""},
|
{Title: "*to be replaced*", Path: ""},
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
return &httpx.HTMLHandler[routeUserContext]{
|
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *custom.BaseContextGaps) (uc userContext, err error) {
|
||||||
Handler: func(r *http.Request) (ruc routeUserContext, err error) {
|
|
||||||
// find the user
|
// find the user
|
||||||
ruc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
uc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil || ruc.AuthUser == nil {
|
if err != nil || uc.AuthUser == nil {
|
||||||
return ruc, err
|
return uc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the gaps
|
// build the gaps
|
||||||
gaps := gaps.Clone()
|
if uc.AuthUser.IsTOTPEnabled() {
|
||||||
if ruc.AuthUser.IsTOTPEnabled() {
|
|
||||||
gaps.Actions[1] = component.MenuItem{
|
gaps.Actions[1] = component.MenuItem{
|
||||||
Title: "Disable Passcode (TOTP)",
|
Title: "Disable Passcode (TOTP)",
|
||||||
Path: "/user/totp/disable/",
|
Path: "/user/totp/disable/",
|
||||||
|
|
@ -68,27 +61,24 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
Path: "/user/totp/enable/",
|
Path: "/user/totp/enable/",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
panel.Dependencies.Custom.Update(&ruc, r, gaps)
|
|
||||||
|
|
||||||
// find the grants
|
// find the grants
|
||||||
grants, err := panel.Dependencies.Policy.User(r.Context(), ruc.AuthUser.User.User)
|
grants, err := panel.Dependencies.Policy.User(r.Context(), uc.AuthUser.User.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruc, err
|
return uc, err
|
||||||
}
|
}
|
||||||
|
|
||||||
ruc.Grants = make([]GrantWithURL, len(grants))
|
uc.Grants = make([]GrantWithURL, len(grants))
|
||||||
for i, grant := range grants {
|
for i, grant := range grants {
|
||||||
ruc.Grants[i].Grant = grant
|
uc.Grants[i].Grant = grant
|
||||||
|
|
||||||
url, err := panel.Dependencies.Next.Next(r.Context(), grant.Slug, "/")
|
url, err := panel.Dependencies.Next.Next(r.Context(), grant.Slug, "/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ruc, err
|
return uc, err
|
||||||
}
|
}
|
||||||
ruc.Grants[i].URL = template.URL(url)
|
uc.Grants[i].URL = template.URL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ruc, err
|
return uc, err
|
||||||
},
|
})
|
||||||
Template: userTemplate,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,8 @@ func (auth *Auth) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "login.html"
|
//go:embed "login.html"
|
||||||
var loginHTMLStr string
|
var loginHTML []byte
|
||||||
var loginTemplate = static.AssetsUser.MustParseShared("login.html", loginHTMLStr)
|
var loginTemplate = custom.ParseForm("login.html", loginHTML, static.AssetsUser)
|
||||||
|
|
||||||
var loginResponse = httpx.Response{
|
var loginResponse = httpx.Response{
|
||||||
ContentType: "text/plain",
|
ContentType: "text/plain",
|
||||||
|
|
@ -131,7 +131,7 @@ var errLoginFailed = errors.New("Login failed")
|
||||||
|
|
||||||
// authLogin implements a view to login a user
|
// authLogin implements a view to login a user
|
||||||
func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
loginTemplate := auth.Dependencies.Custom.Template(loginTemplate)
|
tpl := loginTemplate.Prepare(auth.Dependencies.Custom)
|
||||||
|
|
||||||
return &httpx.Form[*AuthUser]{
|
return &httpx.Form[*AuthUser]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -145,11 +145,11 @@ 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, custom.BaseContextGaps{
|
tpl.Execute(w, r, custom.BaseFormContext{FormContext: context}, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Login", Path: template.URL(r.URL.RequestURI())},
|
{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) {
|
||||||
|
|
|
||||||
|
|
@ -80,21 +80,16 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
||||||
}
|
}
|
||||||
|
|
||||||
// add a handler for the index page
|
// add a handler for the index page
|
||||||
router.Handler(http.MethodGet, route, httpx.HTMLHandler[indexContext]{
|
{
|
||||||
Handler: admin.index,
|
index := admin.index(ctx)
|
||||||
Template: admin.Dependencies.Custom.Template(indexTemplate),
|
router.Handler(http.MethodGet, route, index)
|
||||||
})
|
}
|
||||||
|
|
||||||
// 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]{
|
{
|
||||||
Handler: admin.users,
|
users := admin.users(ctx)
|
||||||
Template: admin.Dependencies.Custom.Template(userTemplate),
|
router.Handler(http.MethodGet, route+"users", users)
|
||||||
})
|
}
|
||||||
|
|
||||||
// add a user create form
|
// add a user create form
|
||||||
{
|
{
|
||||||
|
|
@ -113,32 +108,28 @@ func (admin *Admin) HandleRoute(ctx context.Context, route string) (handler http
|
||||||
router.Handler(http.MethodPost, route+"users/unsetpassword", admin.usersUnsetPasswordHandler(ctx))
|
router.Handler(http.MethodPost, route+"users/unsetpassword", admin.usersUnsetPasswordHandler(ctx))
|
||||||
|
|
||||||
// add a handler for the component page
|
// add a handler for the component page
|
||||||
router.Handler(http.MethodGet, route+"components", httpx.HTMLHandler[componentContext]{
|
{
|
||||||
Handler: admin.components,
|
components := admin.components(ctx)
|
||||||
Template: admin.Dependencies.Custom.Template(componentsTemplate),
|
router.Handler(http.MethodGet, route+"components", components)
|
||||||
})
|
}
|
||||||
|
|
||||||
// add a handler for the component page
|
// add a handler for the ingredients page
|
||||||
router.Handler(http.MethodGet, route+"ingredients/:slug", httpx.HTMLHandler[ingredientsContext]{
|
{
|
||||||
Handler: admin.ingredients,
|
ingredients := admin.ingredients(ctx)
|
||||||
Template: admin.Dependencies.Custom.Template(ingredientsTemplate),
|
router.Handler(http.MethodGet, route+"ingredients/:slug", ingredients)
|
||||||
})
|
}
|
||||||
|
|
||||||
// add a handler for the instance page
|
// add a handler for the instance page
|
||||||
router.Handler(http.MethodGet, route+"instance/:slug", httpx.HTMLHandler[instanceContext]{
|
{
|
||||||
Handler: admin.instance,
|
instance := admin.instance(ctx)
|
||||||
Template: admin.Dependencies.Custom.Template(instanceTemplate),
|
router.Handler(http.MethodGet, route+"instance/:slug", instance)
|
||||||
})
|
}
|
||||||
|
|
||||||
// add a router for the grants pages
|
{
|
||||||
router.Handler(http.MethodGet, route+"grants/:slug", httpx.HTMLHandler[grantsContext]{
|
grants := admin.grants(ctx)
|
||||||
Handler: admin.getGrants,
|
router.Handler(http.MethodGet, route+"grants/:slug", grants)
|
||||||
Template: admin.Dependencies.Custom.Template(grantsTemplate),
|
router.Handler(http.MethodPost, route+"grants/", grants) // NOTE(twiesing): This path is intentionally different!
|
||||||
})
|
}
|
||||||
router.Handler(http.MethodPost, route+"grants/", httpx.HTMLHandler[grantsContext]{
|
|
||||||
Handler: admin.postGrants,
|
|
||||||
Template: admin.Dependencies.Custom.Template(grantsTemplate),
|
|
||||||
})
|
|
||||||
|
|
||||||
// add a router for the login page
|
// add a router for the login page
|
||||||
router.Handler(http.MethodPost, route+"login", admin.loginHandler(ctx))
|
router.Handler(http.MethodPost, route+"login", admin.loginHandler(ctx))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -17,11 +18,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/components.html"
|
//go:embed "html/components.html"
|
||||||
var componentsTemplateString string
|
var componentsHTML []byte
|
||||||
var componentsTemplate = static.AssetsAdmin.MustParseShared(
|
var componentsTemplate = custom.Parse[componentContext]("components.html", componentsHTML, static.AssetsAdmin)
|
||||||
"components.html",
|
|
||||||
componentsTemplateString,
|
|
||||||
)
|
|
||||||
|
|
||||||
type componentContext struct {
|
type componentContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -29,24 +27,23 @@ type componentContext struct {
|
||||||
Analytics lazy.PoolAnalytics
|
Analytics lazy.PoolAnalytics
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) components(r *http.Request) (cp componentContext, err error) {
|
func (admin *Admin) components(ctx context.Context) http.Handler {
|
||||||
admin.Dependencies.Custom.Update(&cp, r, custom.BaseContextGaps{
|
tpl := componentsTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Components", Path: "/admin/components/"},
|
{Title: "Components", Path: "/admin/components/"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandler(func(r *http.Request) (cp componentContext, err error) {
|
||||||
cp.Analytics = *admin.Analytics
|
cp.Analytics = *admin.Analytics
|
||||||
return
|
return
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "html/ingredients.html"
|
//go:embed "html/ingredients.html"
|
||||||
var ingredientsTemplateString string
|
var ingredientsHTML []byte
|
||||||
var ingredientsTemplate = static.AssetsAdmin.MustParseShared(
|
var ingredientsTemplate = custom.Parse[ingredientsContext]("ingredients.html", ingredientsHTML, static.AssetsAdmin)
|
||||||
"ingredients.html",
|
|
||||||
ingredientsTemplateString,
|
|
||||||
)
|
|
||||||
|
|
||||||
type ingredientsContext struct {
|
type ingredientsContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -55,29 +52,34 @@ type ingredientsContext struct {
|
||||||
Analytics *lazy.PoolAnalytics
|
Analytics *lazy.PoolAnalytics
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) ingredients(r *http.Request) (cp ingredientsContext, err error) {
|
func (admin *Admin) ingredients(ctx context.Context) http.Handler {
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
tpl := ingredientsTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
|
|
||||||
admin.Dependencies.Custom.Update(&cp, r, custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
{Title: "Instance", Path: "* to be updated *"},
|
||||||
{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")},
|
{Title: "Ingredients", Path: "* to be updated *"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *custom.BaseContextGaps) (ic ingredientsContext, err error) {
|
||||||
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
|
|
||||||
|
gaps.Crumbs[1] = component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}
|
||||||
|
gaps.Crumbs[2] = component.MenuItem{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")}
|
||||||
|
|
||||||
// find the instance itself!
|
// find the instance itself!
|
||||||
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 ic, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cp, err
|
return ic, err
|
||||||
}
|
}
|
||||||
cp.Instance = instance.Instance
|
ic.Instance = instance.Instance
|
||||||
|
|
||||||
// and get the components
|
// and get the components
|
||||||
cp.Analytics = instance.Info().Analytics
|
ic.Analytics = instance.Info().Analytics
|
||||||
|
|
||||||
return
|
return
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
@ -20,11 +21,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/grants.html"
|
//go:embed "html/grants.html"
|
||||||
var grantsStr string
|
var grantsHTML []byte
|
||||||
var grantsTemplate = static.AssetsAdmin.MustParseShared(
|
var grantsTemplate = custom.Parse[grantsContext]("grants.html", grantsHTML, static.AssetsAdmin)
|
||||||
"grants.html",
|
|
||||||
grantsStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
type grantsContext struct {
|
type grantsContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -39,15 +37,88 @@ type grantsContext struct {
|
||||||
Drupals []string // unusued drupal usernames
|
Drupals []string // unusued drupal usernames
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (err error) {
|
func (admin *Admin) grants(ctx context.Context) http.Handler {
|
||||||
admin.Dependencies.Custom.Update(gc, r, custom.BaseContextGaps{
|
tpl := grantsTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
{Title: "Instance", Path: "*to be updated*"},
|
||||||
{Title: "Grants", Path: template.URL("/admin/instance/" + slug + "/grants/")},
|
{Title: "Grants", Path: "*to be updated*"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *custom.BaseContextGaps) (grantsContext, error) {
|
||||||
|
if r.Method == http.MethodGet {
|
||||||
|
return admin.getGrants(r, gaps)
|
||||||
|
} else {
|
||||||
|
return admin.postGrants(r, gaps)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (admin *Admin) getGrants(r *http.Request, gaps *custom.BaseContextGaps) (gc grantsContext, err error) {
|
||||||
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
|
if err := gc.use(r, gaps, slug, admin); err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := gc.useGrants(r, admin); err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return gc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (admin *Admin) postGrants(r *http.Request, gaps *custom.BaseContextGaps) (gc grantsContext, err error) {
|
||||||
|
// parse the form
|
||||||
|
if err := r.ParseForm(); err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// read out the form values
|
||||||
|
var (
|
||||||
|
slug = r.PostFormValue("slug")
|
||||||
|
delete = r.PostFormValue("action") == "delete"
|
||||||
|
distilleryUser = r.PostFormValue("distillery-user")
|
||||||
|
drupalUser = r.PostFormValue("drupal-user")
|
||||||
|
adminRole = r.PostFormValue("admin") == field.CheckboxChecked
|
||||||
|
)
|
||||||
|
|
||||||
|
// set the common fields
|
||||||
|
if err := gc.use(r, gaps, slug, admin); err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if delete {
|
||||||
|
// delete the user grant
|
||||||
|
err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug)
|
||||||
|
if err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// update the grant
|
||||||
|
err := admin.Dependencies.Policy.Set(r.Context(), models.Grant{
|
||||||
|
User: distilleryUser,
|
||||||
|
Slug: slug,
|
||||||
|
|
||||||
|
DrupalUsername: drupalUser,
|
||||||
|
DrupalAdminRole: adminRole,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
gc.Error = fmt.Sprintf("Unable to update grant for user %s: %s", distilleryUser, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch the grants for the instance
|
||||||
|
if err := gc.useGrants(r, admin); err != nil {
|
||||||
|
return gc, err
|
||||||
|
}
|
||||||
|
return gc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (gc *grantsContext) use(r *http.Request, gaps *custom.BaseContextGaps, slug string, admin *Admin) (err error) {
|
||||||
|
gaps.Crumbs[1] = component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}
|
||||||
|
gaps.Crumbs[2] = component.MenuItem{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)
|
||||||
if err == instances.ErrWissKINotFound {
|
if err == instances.ErrWissKINotFound {
|
||||||
|
|
@ -100,63 +171,3 @@ func (gc *grantsContext) useGrants(r *http.Request, admin *Admin) (err error) {
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) getGrants(r *http.Request) (gc grantsContext, err error) {
|
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
|
||||||
if err := gc.use(r, slug, admin); err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := gc.useGrants(r, admin); err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (admin *Admin) postGrants(r *http.Request) (gc grantsContext, err error) {
|
|
||||||
// parse the form
|
|
||||||
if err := r.ParseForm(); err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// read out the form values
|
|
||||||
var (
|
|
||||||
slug = r.PostFormValue("slug")
|
|
||||||
delete = r.PostFormValue("action") == "delete"
|
|
||||||
distilleryUser = r.PostFormValue("distillery-user")
|
|
||||||
drupalUser = r.PostFormValue("drupal-user")
|
|
||||||
adminRole = r.PostFormValue("admin") == field.CheckboxChecked
|
|
||||||
)
|
|
||||||
|
|
||||||
// set the common fields
|
|
||||||
if err := gc.use(r, slug, admin); err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if delete {
|
|
||||||
// delete the user grant
|
|
||||||
err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug)
|
|
||||||
if err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// update the grant
|
|
||||||
err := admin.Dependencies.Policy.Set(r.Context(), models.Grant{
|
|
||||||
User: distilleryUser,
|
|
||||||
Slug: slug,
|
|
||||||
|
|
||||||
DrupalUsername: drupalUser,
|
|
||||||
DrupalAdminRole: adminRole,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
gc.Error = fmt.Sprintf("Unable to update grant for user %s: %s", distilleryUser, err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the grants for the instance
|
|
||||||
if err := gc.useGrants(r, admin); err != nil {
|
|
||||||
return gc, err
|
|
||||||
}
|
|
||||||
return gc, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,6 @@ import (
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/index.html"
|
|
||||||
var indexTemplateStr string
|
|
||||||
var indexTemplate = static.AssetsAdmin.MustParseShared(
|
|
||||||
"index.html",
|
|
||||||
indexTemplateStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Status produces a new observation of the distillery, and a new information of all instances
|
// Status produces a new observation of the distillery, and a new information of all instances
|
||||||
// The information on all instances is passed the given quick flag.
|
// The information on all instances is passed the given quick flag.
|
||||||
func (admin *Admin) Status(ctx context.Context, QuickInformation bool) (target status.Distillery, information []status.WissKI, err error) {
|
func (admin *Admin) Status(ctx context.Context, QuickInformation bool) (target status.Distillery, information []status.WissKI, err error) {
|
||||||
|
|
@ -79,6 +72,16 @@ func (admin *Admin) Status(ctx context.Context, QuickInformation bool) (target s
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (admin *Admin) Fetch(flags component.FetcherFlags, target *status.Distillery) error {
|
||||||
|
target.Time = time.Now().UTC()
|
||||||
|
target.Config = admin.Config
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed "html/index.html"
|
||||||
|
var indexHTML []byte
|
||||||
|
var indexTemplate = custom.Parse[indexContext]("index.html", indexHTML, static.AssetsAdmin)
|
||||||
|
|
||||||
type indexContext struct {
|
type indexContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
||||||
|
|
@ -86,8 +89,8 @@ type indexContext struct {
|
||||||
Instances []status.WissKI
|
Instances []status.WissKI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
|
func (admin *Admin) index(ctx context.Context) http.Handler {
|
||||||
admin.Dependencies.Custom.Update(&idx, r, custom.BaseContextGaps{
|
tpl := indexTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
},
|
},
|
||||||
|
|
@ -96,12 +99,9 @@ func (admin *Admin) index(r *http.Request) (idx indexContext, err error) {
|
||||||
{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
|
{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *custom.BaseContextGaps) (idx indexContext, err error) {
|
||||||
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
|
idx.Distillery, idx.Instances, err = admin.Status(r.Context(), true)
|
||||||
return
|
return
|
||||||
}
|
})
|
||||||
|
|
||||||
func (admin *Admin) Fetch(flags component.FetcherFlags, target *status.Distillery) error {
|
|
||||||
target.Time = time.Now().UTC()
|
|
||||||
target.Config = admin.Config
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
@ -16,11 +17,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/instance.html"
|
//go:embed "html/instance.html"
|
||||||
var instanceTemplateString string
|
var instanceHTML []byte
|
||||||
var instanceTemplate = static.AssetsAdmin.MustParseShared(
|
var instanceTemplate = custom.Parse[instanceContext]("instance.html", instanceHTML, static.AssetsAdmin)
|
||||||
"instance.html",
|
|
||||||
instanceTemplateString,
|
|
||||||
)
|
|
||||||
|
|
||||||
type instanceContext struct {
|
type instanceContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -29,35 +27,42 @@ type instanceContext struct {
|
||||||
Info status.WissKI
|
Info status.WissKI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) instance(r *http.Request) (is instanceContext, err error) {
|
func (admin *Admin) instance(ctx context.Context) http.Handler {
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
tpl := instanceTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
|
|
||||||
admin.Dependencies.Custom.Update(&is, r, custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Instance", Path: template.URL("/admin/instance/" + slug)},
|
{Title: "Instance", Path: "*to be replaced*"},
|
||||||
},
|
},
|
||||||
Actions: []component.MenuItem{
|
Actions: []component.MenuItem{
|
||||||
{Title: "Grants", Path: template.URL("/admin/grants/" + slug)},
|
{Title: "Grants", Path: "*to be replaced*"},
|
||||||
{Title: "Ingredients", Path: template.URL("/admin/ingredients/" + slug), Priority: component.SmallButton},
|
{Title: "Ingredients", Path: "*to be replaced*", Priority: component.SmallButton},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *custom.BaseContextGaps) (ic instanceContext, err error) {
|
||||||
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
|
|
||||||
|
gaps.Crumbs[1] = component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}
|
||||||
|
|
||||||
|
gaps.Actions[0] = component.MenuItem{Title: "Grants", Path: template.URL("/admin/grants/" + slug)}
|
||||||
|
gaps.Actions[1] = component.MenuItem{Title: "Ingredients", Path: template.URL("/admin/ingredients/" + slug), Priority: component.SmallButton}
|
||||||
|
|
||||||
// find the instance itself!
|
// find the instance itself!
|
||||||
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 ic, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return is, err
|
return ic, err
|
||||||
}
|
}
|
||||||
is.Instance = instance.Instance
|
ic.Instance = instance.Instance
|
||||||
|
|
||||||
// get some more info about the wisski
|
// get some more info about the wisski
|
||||||
is.Info, err = instance.Info().Information(r.Context(), false)
|
ic.Info, err = instance.Info().Information(r.Context(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return is, err
|
return ic, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,21 +18,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/users.html"
|
//go:embed "html/users.html"
|
||||||
var userTemplateString string
|
var usersHTML []byte
|
||||||
var userTemplate = static.AssetsAdmin.MustParseShared(
|
var usersTemplate = custom.Parse[usersContext]("user.html", usersHTML, static.AssetsAdmin)
|
||||||
"users.html",
|
|
||||||
userTemplateString,
|
|
||||||
)
|
|
||||||
|
|
||||||
type userContext struct {
|
type usersContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
||||||
Error string
|
Error string
|
||||||
Users []*auth.AuthUser
|
Users []*auth.AuthUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
func (admin *Admin) users(ctx context.Context) http.Handler {
|
||||||
admin.Dependencies.Custom.Update(&uc, r, custom.BaseContextGaps{
|
tpl := usersTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Users", Path: "/admin/users/"},
|
{Title: "Users", Path: "/admin/users/"},
|
||||||
|
|
@ -42,17 +39,16 @@ func (admin *Admin) users(r *http.Request) (uc userContext, err error) {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandler(func(r *http.Request) (uc usersContext, err error) {
|
||||||
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())
|
||||||
return
|
return
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "html/user_create.html"
|
//go:embed "html/user_create.html"
|
||||||
var userCreateTemplateString string
|
var userCreateHTML []byte
|
||||||
var userCreateTemplate = static.AssetsAdmin.MustParseShared(
|
var userCreateTemplate = custom.ParseForm("user_create.html", userCreateHTML, static.AssetsAdmin)
|
||||||
"user_create.html",
|
|
||||||
userCreateTemplateString,
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCreateInvalidUsername = errors.New("invalid username")
|
errCreateInvalidUsername = errors.New("invalid username")
|
||||||
|
|
@ -66,14 +62,13 @@ 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)
|
tpl := userCreateTemplate.Prepare(admin.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Admin", Path: "/admin/"},
|
{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Users", Path: "/admin/users"},
|
{Title: "Users", Path: "/admin/users"},
|
||||||
{Title: "Create", Path: "/admin/users/create"},
|
{Title: "Create", Path: "/admin/users/create"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
return &httpx.Form[createUserResult]{
|
return &httpx.Form[createUserResult]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -83,10 +78,8 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: userCreateTemplate,
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
|
RenderTemplateContext: custom.FormTemplateContext(tpl),
|
||||||
return admin.Dependencies.Custom.NewForm(ctx, r, gaps)
|
|
||||||
},
|
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "public.html"
|
//go:embed "public.html"
|
||||||
var publicHTMLStr string
|
var publicHTML []byte
|
||||||
var publicTemplate = static.AssetsDefault.MustParseShared("public.html", publicHTMLStr)
|
var publicTemplate = custom.Parse[publicContext]("public.html", publicHTML, static.AssetsDefault)
|
||||||
|
|
||||||
type publicContext struct {
|
type publicContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -25,25 +25,20 @@ type publicContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
gaps := custom.BaseContextGaps{
|
tpl := publicTemplate.Prepare(home.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "WissKI Distillery", Path: "/"},
|
{Title: "WissKI Distillery", Path: "/"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
return httpx.HTMLHandler[publicContext]{
|
return tpl.HTMLHandler(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!
|
||||||
if strings.TrimSuffix(r.URL.Path, "/") != "" {
|
if strings.TrimSuffix(r.URL.Path, "/") != "" {
|
||||||
return pc, httpx.ErrNotFound
|
return pc, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
home.Dependencies.Custom.Update(&pc, r, gaps)
|
|
||||||
|
|
||||||
pc.Instances = home.homeInstances.Get(nil)
|
pc.Instances = home.homeInstances.Get(nil)
|
||||||
pc.SelfRedirect = home.Config.SelfRedirect.String()
|
pc.SelfRedirect = home.Config.SelfRedirect.String()
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
})
|
||||||
Template: home.Dependencies.Custom.Template(publicTemplate),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"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/internal/dis/component/control/static/custom"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static/custom"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
@ -27,26 +26,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "legal.html"
|
//go:embed "legal.html"
|
||||||
var legalTemplateString string
|
var legalHTML []byte
|
||||||
var legalTemplate = static.AssetsDefault.MustParseShared("legal.html", legalTemplateString)
|
var legalTemplate = custom.Parse[legalContext]("legal.html", legalHTML, static.AssetsDefault)
|
||||||
|
|
||||||
func (legal *Legal) Routes() component.Routes {
|
|
||||||
return component.Routes{
|
|
||||||
Prefix: "/legal/",
|
|
||||||
Exact: true,
|
|
||||||
|
|
||||||
CSRF: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
|
||||||
legalTemplate := legal.Dependencies.Custom.Template(legalTemplate)
|
|
||||||
|
|
||||||
return httpx.HTMLHandler[legalContext]{
|
|
||||||
Handler: legal.context,
|
|
||||||
Template: legalTemplate,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type legalContext struct {
|
type legalContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -58,17 +39,29 @@ type legalContext struct {
|
||||||
AssetsDisclaimer string
|
AssetsDisclaimer string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (legal *Legal) context(r *http.Request) (lc legalContext, err error) {
|
func (legal *Legal) Routes() component.Routes {
|
||||||
legal.Dependencies.Custom.Update(&lc, r, custom.BaseContextGaps{
|
return component.Routes{
|
||||||
|
Prefix: "/legal/",
|
||||||
|
Exact: true,
|
||||||
|
|
||||||
|
CSRF: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
|
tpl := legalTemplate.Prepare(legal.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Legal", Path: "/legal/"},
|
{Title: "Legal", Path: "/legal/"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return tpl.HTMLHandler(func(r *http.Request) (lc legalContext, err error) {
|
||||||
lc.LegalNotices = cli.LegalNotices
|
lc.LegalNotices = cli.LegalNotices
|
||||||
|
|
||||||
lc.CSRFCookie = control.CSRFCookie
|
lc.CSRFCookie = control.CSRFCookie
|
||||||
lc.SessionCookie = control.SessionCookie
|
lc.SessionCookie = control.SessionCookie
|
||||||
lc.AssetsDisclaimer = static.AssetsDisclaimer
|
lc.AssetsDisclaimer = static.AssetsDisclaimer
|
||||||
|
|
||||||
return
|
return
|
||||||
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ 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/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/pkg/httpx"
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
gmmeta "github.com/yuin/goldmark-meta"
|
gmmeta "github.com/yuin/goldmark-meta"
|
||||||
|
|
@ -113,8 +112,8 @@ func Items() ([]Item, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "news.html"
|
//go:embed "news.html"
|
||||||
var newsHTMLStr string
|
var newsHTML []byte
|
||||||
var newsTemplate = static.AssetsDefault.MustParseShared("news.html", newsHTMLStr)
|
var newsTemplate = custom.Parse[newsContext]("news.html", newsHTML, static.AssetsDefault)
|
||||||
|
|
||||||
type newsContext struct {
|
type newsContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -123,24 +122,19 @@ 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) {
|
||||||
gaps := custom.BaseContextGaps{
|
tpl := newsTemplate.Prepare(news.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "News", Path: "/news/"},
|
{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")
|
||||||
}
|
}
|
||||||
|
|
||||||
return httpx.HTMLHandler[newsContext]{
|
return tpl.HTMLHandler(func(r *http.Request) (nc newsContext, err error) {
|
||||||
Handler: func(r *http.Request) (nc newsContext, err error) {
|
|
||||||
news.Dependencies.Custom.Update(&nc, r, gaps)
|
|
||||||
nc.Items, err = items, itemsErr
|
nc.Items, err = items, itemsErr
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
}), nil
|
||||||
Template: news.Dependencies.Custom.Template(newsTemplate),
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,19 +48,18 @@ type BaseContextGaps struct {
|
||||||
Actions []component.MenuItem
|
Actions []component.MenuItem
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bcg BaseContextGaps) Clone() BaseContextGaps {
|
func (bcg BaseContextGaps) clone() BaseContextGaps {
|
||||||
return BaseContextGaps{
|
return BaseContextGaps{
|
||||||
Crumbs: slices.Clone(bcg.Crumbs),
|
Crumbs: slices.Clone(bcg.Crumbs),
|
||||||
Actions: slices.Clone(bcg.Actions),
|
Actions: slices.Clone(bcg.Actions),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use updates this context to use the values from the given base.
|
// update updates an embedded BaseContext field in context.
|
||||||
//
|
func (custom *Custom) update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
|
||||||
// The given request *must not* be nil.
|
tc := reflect.ValueOf(context).
|
||||||
//
|
Elem().FieldByName(baseContextName).Addr().
|
||||||
// For convenience the passed context is also returned.
|
Interface().(*BaseContext)
|
||||||
func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps) *BaseContext {
|
|
||||||
// tc.custom = custom
|
// tc.custom = custom
|
||||||
tc.inited = true
|
tc.inited = true
|
||||||
tc.requestWasNil = r == nil
|
tc.requestWasNil = r == nil
|
||||||
|
|
@ -77,7 +76,7 @@ func (tc *BaseContext) use(custom *Custom, r *http.Request, gaps BaseContextGaps
|
||||||
tc.Menu = custom.BuildMenu(r)
|
tc.Menu = custom.BuildMenu(r)
|
||||||
|
|
||||||
// build the breadcrumbs
|
// build the breadcrumbs
|
||||||
tc.BaseContextGaps = gaps.Clone()
|
tc.BaseContextGaps = bcg.clone()
|
||||||
last := len(tc.Crumbs) - 1
|
last := len(tc.Crumbs) - 1
|
||||||
for i := range tc.Crumbs {
|
for i := range tc.Crumbs {
|
||||||
tc.Crumbs[i].Active = i == last
|
tc.Crumbs[i].Active = i == last
|
||||||
|
|
@ -97,25 +96,6 @@ func (bc BaseContext) DoInitCheck() template.HTML {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewForm is like New, but returns a new BaseFormContext
|
|
||||||
func (custom *Custom) NewForm(context httpx.FormContext, r *http.Request, bcg BaseContextGaps) (ctx BaseFormContext) {
|
|
||||||
ctx.FormContext = context
|
|
||||||
ctx.use(custom, r, bcg)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update updates an embedded BaseContext field in context.
|
|
||||||
//
|
|
||||||
// Assumes that context is a pointer to a struct type.
|
|
||||||
// If this is not the case, might call panic().
|
|
||||||
func (custom *Custom) Update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
|
|
||||||
ctx := reflect.ValueOf(context).
|
|
||||||
Elem().FieldByName(baseContextName).Addr().
|
|
||||||
Interface().(*BaseContext)
|
|
||||||
ctx.use(custom, r, bcg)
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseFormContext combines BaseContext and FormContext
|
// BaseFormContext combines BaseContext and FormContext
|
||||||
type BaseFormContext struct {
|
type BaseFormContext struct {
|
||||||
BaseContext
|
BaseContext
|
||||||
|
|
|
||||||
139
internal/dis/component/control/static/custom/new.go
Normal file
139
internal/dis/component/control/static/custom/new.go
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package custom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parsed represents a parsed template that receives an underlying context of type C
|
||||||
|
type Parsed[C any] struct {
|
||||||
|
template *template.Template
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse creates a new Parsed from a template source.
|
||||||
|
// Parse calls panic() when parsing fails.
|
||||||
|
func Parse[C any](name string, source []byte, Assets static.Assets) Parsed[C] {
|
||||||
|
return Parsed[C]{
|
||||||
|
template: Assets.MustParseShared(name, string(source)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare prepares this template for use inside a concrete handler.
|
||||||
|
// gaps must either be of length 0 or length 1 and may pre-fill gaps to be used when executing the template later.
|
||||||
|
func (p *Parsed[C]) Prepare(custom *Custom, gaps ...BaseContextGaps) *Template[C] {
|
||||||
|
wrap := Template[C]{
|
||||||
|
custom: custom,
|
||||||
|
template: custom.Template(p.template),
|
||||||
|
}
|
||||||
|
if len(gaps) > 1 {
|
||||||
|
panic("WrapTemplate: must provide either 1 or no gaps")
|
||||||
|
}
|
||||||
|
if len(gaps) == 1 {
|
||||||
|
wrap.gaps = gaps[0]
|
||||||
|
}
|
||||||
|
return &wrap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tempalte represents an executable template.
|
||||||
|
type Template[C any] struct {
|
||||||
|
custom *Custom
|
||||||
|
template *template.Template
|
||||||
|
gaps BaseContextGaps
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template returns a template that, if executed together with the context by the Context method, produces the desired result.
|
||||||
|
func (tw *Template[C]) Template() *template.Template {
|
||||||
|
return tw.template
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context generates a context for a given request that can be used to execute the provided template.
|
||||||
|
func (tw *Template[C]) Context(r *http.Request, c C, gaps ...BaseContextGaps) any {
|
||||||
|
// make the gaps something
|
||||||
|
if len(gaps) > 1 {
|
||||||
|
panic("Context: must provide either 1 or no gaps")
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the context with gaps
|
||||||
|
{
|
||||||
|
g := tw.gaps
|
||||||
|
if len(gaps) == 1 {
|
||||||
|
g = gaps[0]
|
||||||
|
}
|
||||||
|
tw.custom.update(&c, r, g)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseForm is like Parse[BaseFormContext]
|
||||||
|
var ParseForm = Parse[BaseFormContext]
|
||||||
|
|
||||||
|
// FormTemplateContext returns a new handler for a form with the given base context
|
||||||
|
func FormTemplateContext(tw *Template[BaseFormContext]) func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
return func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
return tw.Context(r, BaseFormContext{FormContext: ctx})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MappedHandler returns a new handler that maps the incoming context via f
|
||||||
|
func MappedHandler[In, Out any](tw *Template[Out], f func(ctx In, r *http.Request) (Out, BaseContextGaps)) func(ctx In, r *http.Request) any {
|
||||||
|
// TODO: Should this one be removed?
|
||||||
|
return func(ctx In, r *http.Request) any {
|
||||||
|
c, g := f(ctx, r)
|
||||||
|
return tw.Context(r, c, g)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hander returns a function that returns a context for the given template
|
||||||
|
func (tw *Template[C]) Handler(f func(r *http.Request) (C, error)) func(r *http.Request) (any, error) {
|
||||||
|
// TODO: Should this one be removed?
|
||||||
|
return tw.HandlerWithGaps(func(r *http.Request, gaps *BaseContextGaps) (C, error) {
|
||||||
|
return f(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTMLHandler returns a new HTMLHandler for this request
|
||||||
|
func (tw *Template[C]) HTMLHandler(f func(r *http.Request) (C, error)) httpx.HTMLHandler[any] {
|
||||||
|
return httpx.HTMLHandler[any]{
|
||||||
|
Handler: tw.Handler(f),
|
||||||
|
Template: tw.Template(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerWithGaps works like handler, but additionally receives a gaps object to update.
|
||||||
|
func (tw *Template[C]) HandlerWithGaps(f func(r *http.Request, gaps *BaseContextGaps) (C, error)) func(r *http.Request) (any, error) {
|
||||||
|
// TODO: Drop this variant?
|
||||||
|
var zero C
|
||||||
|
return func(r *http.Request) (any, error) {
|
||||||
|
g := tw.gaps.clone()
|
||||||
|
c, err := f(r, &g)
|
||||||
|
if err != nil {
|
||||||
|
return zero, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the context
|
||||||
|
return tw.Context(r, c, g), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *Template[C]) HTMLHandlerWithGaps(f func(r *http.Request, gaps *BaseContextGaps) (C, error)) httpx.HTMLHandler[any] {
|
||||||
|
return httpx.HTMLHandler[any]{
|
||||||
|
Handler: tw.HandlerWithGaps(f),
|
||||||
|
Template: tw.Template(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute executes this template with the given context
|
||||||
|
func (tw *Template[C]) Execute(w http.ResponseWriter, r *http.Request, c C, gaps ...BaseContextGaps) error {
|
||||||
|
return tw.ExecuteWithError(w, r, c, nil, gaps...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecuteWithError executes this template, or the default error handler if err != nil
|
||||||
|
func (tw *Template[C]) ExecuteWithError(w http.ResponseWriter, r *http.Request, c C, err error, gaps ...BaseContextGaps) error {
|
||||||
|
// TODO: Drop this variant?
|
||||||
|
// TODO: This should be removed!
|
||||||
|
return httpx.WriteHTML(tw.Context(r, c, gaps...), err, tw.template, "", w, r)
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ import (
|
||||||
"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"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
|
@ -50,8 +49,8 @@ func (resolver *Resolver) Routes() component.Routes {
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "resolver.html"
|
//go:embed "resolver.html"
|
||||||
var resolverHTMLStr string
|
var resolverHTML []byte
|
||||||
var resolverTemplate = static.AssetsDefault.MustParseShared("resolver.html", resolverHTMLStr)
|
var resolverTemplate = custom.Parse[resolverContext]("resolver.html", resolverHTML, static.AssetsDefault)
|
||||||
|
|
||||||
type resolverContext struct {
|
type resolverContext struct {
|
||||||
custom.BaseContext
|
custom.BaseContext
|
||||||
|
|
@ -59,13 +58,11 @@ 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)
|
tpl := resolverTemplate.Prepare(resolver.Dependencies.Custom, custom.BaseContextGaps{
|
||||||
gaps := custom.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
Crumbs: []component.MenuItem{
|
||||||
{Title: "Resolver", Path: "/wisski/get/"},
|
{Title: "Resolver", Path: "/wisski/get/"},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
logger := zerolog.Ctx(ctx)
|
logger := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
var p wdresolve.ResolveHandler
|
var p wdresolve.ResolveHandler
|
||||||
|
|
@ -75,13 +72,11 @@ 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, gaps)
|
|
||||||
|
|
||||||
if !resolver.Dependencies.Auth.Has(auth.User, r) {
|
if !resolver.Dependencies.Auth.Has(auth.User, r) {
|
||||||
ctx.IndexContext.Prefixes = nil
|
ctx.IndexContext.Prefixes = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
httpx.WriteHTML(ctx, nil, resolverTemplate, "", w, r)
|
tpl.Execute(w, r, ctx)
|
||||||
}
|
}
|
||||||
p.TrustXForwardedProto = true
|
p.TrustXForwardedProto = true
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue