Refactor html templates
This commit entirely refactors the use of html templates. Instead of inheriting from a shared template, we insert the results into a base template.
This commit is contained in:
parent
6ede99d7c6
commit
d235ee4e5c
59 changed files with 869 additions and 777 deletions
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"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/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
|
@ -17,7 +17,7 @@ type Auth struct {
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
SQL *sql.SQL
|
SQL *sql.SQL
|
||||||
UserDeleteHooks []component.UserDeleteHook
|
UserDeleteHooks []component.UserDeleteHook
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
}
|
}
|
||||||
|
|
||||||
store lazy.Lazy[sessions.Store]
|
store lazy.Lazy[sessions.Store]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "form.html" . }}
|
||||||
{{ define "form/title" }}Login Required{{ end }}
|
|
||||||
{{ define "form/button" }}Login{{ end }}
|
{{ define "form/button" }}Login{{ end }}
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div class="pure-form-group">
|
<div class="pure-form-group">
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/next"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/next"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
||||||
"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/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2/sshkeys"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2/sshkeys"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
@ -20,7 +20,7 @@ type UserPanel struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
Auth *auth.Auth
|
Auth *auth.Auth
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
Policy *policy.Policy
|
Policy *policy.Policy
|
||||||
Instances *instances.Instances
|
Instances *instances.Instances
|
||||||
Next *next.Next
|
Next *next.Next
|
||||||
|
|
@ -106,30 +106,25 @@ func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Han
|
||||||
}
|
}
|
||||||
|
|
||||||
type userFormContext struct {
|
type userFormContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
httpx.FormContext
|
httpx.FormContext
|
||||||
|
|
||||||
User *models.User
|
User *models.User
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) UserFormContext2(tpl *templates.Template[userFormContext], last component.MenuItem, gaps ...templates.BaseContextGaps) func(ctx httpx.FormContext, r *http.Request) any {
|
func (panel *UserPanel) UserFormContext(tpl *templating.Template[userFormContext], last component.MenuItem, funcs ...templating.FlagFunc) func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
var g templates.BaseContextGaps
|
funcs = append(funcs, func(flags templating.Flags, r *http.Request) templating.Flags {
|
||||||
if len(gaps) > 1 {
|
flags.Crumbs = append(flags.Crumbs, component.MenuItem{})
|
||||||
panic("UserFormContext2: gaps must be of length 0 or 1")
|
copy(flags.Crumbs[1:], flags.Crumbs)
|
||||||
}
|
flags.Crumbs[0] = component.MenuItem{Title: "User", Path: "/user/"}
|
||||||
if len(gaps) == 1 {
|
return flags
|
||||||
g = gaps[0]
|
})
|
||||||
}
|
|
||||||
g.Crumbs = []component.MenuItem{
|
|
||||||
{Title: "User", Path: "/user/"},
|
|
||||||
last,
|
|
||||||
}
|
|
||||||
|
|
||||||
return templates.MappedHandler(tpl, func(ctx httpx.FormContext, r *http.Request) (userFormContext, templates.BaseContextGaps) {
|
return func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
uctx := userFormContext{FormContext: ctx}
|
uctx := userFormContext{FormContext: ctx}
|
||||||
if user, err := panel.Dependencies.Auth.UserOf(r); err == nil {
|
if user, err := panel.Dependencies.Auth.UserOf(r); err == nil {
|
||||||
uctx.User = &user.User
|
uctx.User = &user.User
|
||||||
}
|
}
|
||||||
return uctx, g
|
return tpl.Context(r, uctx, funcs...)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,19 @@ 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/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
templating "github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/password.html"
|
//go:embed "templates/password.html"
|
||||||
var passwordHTML []byte
|
var passwordHTML []byte
|
||||||
var passwordTemplate = templating.Parse[userFormContext]("password.html", passwordHTML, assets.AssetsUser)
|
var passwordTemplate = templating.Parse[userFormContext](
|
||||||
|
"password.html", passwordHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Change Password"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errPasswordsNotIdentical = errors.New("passwords are not identical")
|
errPasswordsNotIdentical = errors.New("passwords are not identical")
|
||||||
|
|
@ -40,7 +45,7 @@ func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: tpl.Template(),
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext2(tpl, component.MenuItem{Title: "Change Password", Path: "/user/password/"}),
|
RenderTemplateContext: panel.UserFormContext(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"]
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ 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/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"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"
|
||||||
|
|
@ -22,10 +22,15 @@ import (
|
||||||
|
|
||||||
//go:embed "templates/ssh.html"
|
//go:embed "templates/ssh.html"
|
||||||
var sshHTML []byte
|
var sshHTML []byte
|
||||||
var sshTemplate = templates.Parse[SSHTemplateContext]("ssh.html", sshHTML, assets.AssetsUser)
|
var sshTemplate = templating.Parse[SSHTemplateContext](
|
||||||
|
"ssh.html", sshHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("SSH Keys"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
type SSHTemplateContext struct {
|
type SSHTemplateContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
Keys []models.Keys
|
Keys []models.Keys
|
||||||
|
|
||||||
|
|
@ -37,15 +42,16 @@ type SSHTemplateContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
func (panel *UserPanel) sshRoute(ctx context.Context) http.Handler {
|
||||||
tpl := sshTemplate.Prepare(panel.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := sshTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
panel.Dependencies.Templating,
|
||||||
{Title: "User", Path: "/user/"},
|
templating.Crumbs(
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
component.MenuItem{Title: "User", Path: "/user/"},
|
||||||
},
|
component.MenuItem{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
Actions: []component.MenuItem{
|
),
|
||||||
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
templating.Actions(
|
||||||
},
|
component.MenuItem{Title: "Add New Key", Path: "/user/ssh/add/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandler(func(r *http.Request) (sc SSHTemplateContext, err error) {
|
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)
|
||||||
|
|
@ -114,7 +120,11 @@ func (panel *UserPanel) sshDeleteRoute(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
//go:embed "templates/ssh_add.html"
|
//go:embed "templates/ssh_add.html"
|
||||||
var sshAddHTML []byte
|
var sshAddHTML []byte
|
||||||
var sshAddTemplate = templates.ParseForm("ssh_add.html", sshAddHTML, assets.AssetsUser)
|
var sshAddTemplate = templating.ParseForm(
|
||||||
|
"ssh_add.html", sshAddHTML, httpx.FormTemplate,
|
||||||
|
templating.Title("Add SSH Key"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
type addKeyResult struct {
|
type addKeyResult struct {
|
||||||
User *auth.AuthUser
|
User *auth.AuthUser
|
||||||
|
|
@ -123,13 +133,14 @@ type addKeyResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
||||||
tpl := sshAddTemplate.Prepare(panel.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := sshAddTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
panel.Dependencies.Templating,
|
||||||
{Title: "User", Path: "/user/"},
|
templating.Crumbs(
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
component.MenuItem{Title: "User", Path: "/user/"},
|
||||||
{Title: "Add New Key", Path: "/user/ssh/add/"},
|
component.MenuItem{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
},
|
component.MenuItem{Title: "Add New Key", Path: "/user/ssh/add/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return &httpx.Form[addKeyResult]{
|
return &httpx.Form[addKeyResult]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -139,7 +150,7 @@ func (panel *UserPanel) sshAddRoute(ctx context.Context) http.Handler {
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: tpl.Template(),
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: templates.FormTemplateContext(tpl),
|
RenderTemplateContext: templating.FormTemplateContext(tpl),
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,2 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "form.html" . }}
|
||||||
{{ define "form/title" }}Change Password{{ end }}
|
|
||||||
{{ define "form/button" }}Update{{ end }}
|
{{ define "form/button" }}Update{{ end }}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}SSH Keys{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
This page allows you to add, view and remove ssh keys to and from your distillery account.
|
This page allows you to add, view and remove ssh keys to and from your distillery account.
|
||||||
|
|
@ -101,4 +96,3 @@ Host {{ .Domain }}.proxy
|
||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
{{ template "_form.html" . }}
|
|
||||||
{{ define "form/title" }}Add SSH Key{{ end }}
|
|
||||||
{{ define "form/button" }}Add{{ end }}
|
{{ define "form/button" }}Add{{ end }}
|
||||||
|
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "form.html" . }}
|
||||||
{{ define "form/title" }}Disable TOTP{{ end }}
|
|
||||||
{{ define "form/button" }}Disable{{ end }}
|
{{ define "form/button" }}Disable{{ end }}
|
||||||
|
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "form.html" . }}
|
||||||
{{ define "form/title" }}Enable TOTP{{ end }}
|
|
||||||
{{ define "form/button" }}Enable{{ end }}
|
{{ define "form/button" }}Enable{{ end }}
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
{{ template "_form.html" . }}
|
|
||||||
{{ define "form/title" }}Enable TOTP{{ end }}
|
|
||||||
{{ define "form/button" }}Enable{{ end }}
|
{{ define "form/button" }}Enable{{ end }}
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}{{ .User.User }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
<ul>
|
<ul>
|
||||||
|
|
@ -80,5 +76,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -8,7 +8,7 @@ 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/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"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"
|
||||||
|
|
||||||
|
|
@ -17,7 +17,12 @@ import (
|
||||||
|
|
||||||
//go:embed "templates/totp_enable.html"
|
//go:embed "templates/totp_enable.html"
|
||||||
var totpEnableHTML []byte
|
var totpEnableHTML []byte
|
||||||
var totpEnable = templates.Parse[userFormContext]("totp_enable.html", totpEnableHTML, assets.AssetsUser)
|
var totpEnable = templating.Parse[userFormContext](
|
||||||
|
"totp_enable.html", totpEnableHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Enable TOTP"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
tpl := totpEnable.Prepare(panel.Dependencies.Templating)
|
tpl := totpEnable.Prepare(panel.Dependencies.Templating)
|
||||||
|
|
@ -34,7 +39,7 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
|
|
||||||
RenderTemplate: tpl.Template(),
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext2(tpl, component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"}),
|
RenderTemplateContext: panel.UserFormContext(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"]
|
||||||
|
|
@ -69,7 +74,12 @@ func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
//go:embed "templates/totp_enroll.html"
|
//go:embed "templates/totp_enroll.html"
|
||||||
var totpEnrollHTML []byte
|
var totpEnrollHTML []byte
|
||||||
var totpEnrollTemplate = templates.Parse[totpEnrollContext]("totp_enroll.html", totpEnrollHTML, assets.AssetsUser)
|
var totpEnrollTemplate = templating.Parse[totpEnrollContext](
|
||||||
|
"totp_enroll.html", totpEnrollHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Enable TOTP"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
type totpEnrollContext struct {
|
type totpEnrollContext struct {
|
||||||
userFormContext
|
userFormContext
|
||||||
|
|
@ -80,12 +90,13 @@ type totpEnrollContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
tpl := totpEnrollTemplate.Prepare(panel.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := totpEnrollTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
panel.Dependencies.Templating,
|
||||||
{Title: "User", Path: "/user/"},
|
templating.Crumbs(
|
||||||
{Title: "Enable TOTP", Path: "/user/totp/enable/"},
|
component.MenuItem{Title: "User", Path: "/user/"},
|
||||||
},
|
component.MenuItem{Title: "Enable TOTP", Path: "/user/totp/enable/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -98,9 +109,7 @@ func (panel *UserPanel) routeTOTPEnroll(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()
|
||||||
},
|
},
|
||||||
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
RenderTemplateContext: func(context httpx.FormContext, r *http.Request) any {
|
||||||
// TODO: Do we want to reuse the same function here?
|
|
||||||
|
|
||||||
user, err := panel.Dependencies.Auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
||||||
ctx := totpEnrollContext{
|
ctx := totpEnrollContext{
|
||||||
|
|
@ -120,8 +129,10 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
ctx.TOTPURL = template.URL(secret.URL())
|
ctx.TOTPURL = template.URL(secret.URL())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tpl.Execute(w, r, ctx)
|
|
||||||
|
return tpl.Context(r, ctx)
|
||||||
},
|
},
|
||||||
|
RenderTemplate: tpl.Template(),
|
||||||
|
|
||||||
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"]
|
||||||
|
|
@ -156,7 +167,12 @@ func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
//go:embed "templates/totp_disable.html"
|
//go:embed "templates/totp_disable.html"
|
||||||
var totpDisableHTML []byte
|
var totpDisableHTML []byte
|
||||||
var totpDisableTemplate = templates.Parse[userFormContext]("totp_disable.html", totpDisableHTML, assets.AssetsUser)
|
var totpDisableTemplate = templating.Parse[userFormContext](
|
||||||
|
"totp_disable.html", totpDisableHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Disable TOTP"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
||||||
tpl := totpDisableTemplate.Prepare(panel.Dependencies.Templating)
|
tpl := totpDisableTemplate.Prepare(panel.Dependencies.Templating)
|
||||||
|
|
@ -173,7 +189,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: tpl.Template(),
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: panel.UserFormContext2(tpl, component.MenuItem{Title: "Disable TOTP", Path: "/user/totp/disable/"}),
|
RenderTemplateContext: panel.UserFormContext(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"]
|
||||||
|
|
|
||||||
|
|
@ -10,16 +10,20 @@ 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/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/user.html"
|
//go:embed "templates/user.html"
|
||||||
var userHTML []byte
|
var userHTML []byte
|
||||||
var userTemplate = templates.Parse[userContext]("user.html", userHTML, assets.AssetsUser)
|
var userTemplate = templating.Parse[userContext](
|
||||||
|
"user.html", userHTML, nil,
|
||||||
|
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
type userContext struct {
|
type userContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
*auth.AuthUser
|
*auth.AuthUser
|
||||||
|
|
||||||
Grants []GrantWithURL
|
Grants []GrantWithURL
|
||||||
|
|
@ -31,41 +35,47 @@ type GrantWithURL struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
tpl := userTemplate.Prepare(panel.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := userTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
panel.Dependencies.Templating,
|
||||||
{Title: "User", Path: "/user/"},
|
templating.Crumbs(
|
||||||
},
|
component.MenuItem{Title: "User", Path: "/user/"},
|
||||||
Actions: []component.MenuItem{
|
),
|
||||||
{Title: "Change Password", Path: "/user/password/"},
|
templating.Actions(
|
||||||
{Title: "*to be replaced*", Path: ""},
|
component.MenuItem{Title: "Change Password", Path: "/user/password/"},
|
||||||
{Title: "SSH Keys", Path: "/user/ssh/"},
|
component.DummyMenuItem,
|
||||||
},
|
component.MenuItem{Title: "SSH Keys", Path: "/user/ssh/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *templates.BaseContextGaps) (uc userContext, err error) {
|
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (uc userContext, funcs []templating.FlagFunc, err error) {
|
||||||
// find the user
|
// find the user
|
||||||
uc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
uc.AuthUser, err = panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil || uc.AuthUser == nil {
|
if err != nil || uc.AuthUser == nil {
|
||||||
return uc, err
|
return uc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// build the gaps
|
// replace the totp action in the menu
|
||||||
|
var totpAction component.MenuItem
|
||||||
if uc.AuthUser.IsTOTPEnabled() {
|
if uc.AuthUser.IsTOTPEnabled() {
|
||||||
gaps.Actions[1] = component.MenuItem{
|
totpAction = component.MenuItem{
|
||||||
Title: "Disable Passcode (TOTP)",
|
Title: "Disable Passcode (TOTP)",
|
||||||
Path: "/user/totp/disable/",
|
Path: "/user/totp/disable/",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gaps.Actions[1] = component.MenuItem{
|
totpAction = component.MenuItem{
|
||||||
Title: "Enable Passcode (TOTP)",
|
Title: "Enable Passcode (TOTP)",
|
||||||
Path: "/user/totp/enable/",
|
Path: "/user/totp/enable/",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
funcs = []templating.FlagFunc{
|
||||||
|
templating.ReplaceAction(1, totpAction),
|
||||||
|
templating.Title(uc.AuthUser.User.User),
|
||||||
|
}
|
||||||
|
|
||||||
// find the grants
|
// find the grants
|
||||||
grants, err := panel.Dependencies.Policy.User(r.Context(), uc.AuthUser.User.User)
|
grants, err := panel.Dependencies.Policy.User(r.Context(), uc.AuthUser.User.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uc, err
|
return uc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
uc.Grants = make([]GrantWithURL, len(grants))
|
uc.Grants = make([]GrantWithURL, len(grants))
|
||||||
|
|
@ -74,11 +84,11 @@ func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
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 uc, err
|
return uc, nil, err
|
||||||
}
|
}
|
||||||
uc.Grants[i].URL = template.URL(url)
|
uc.Grants[i].URL = template.URL(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return uc, err
|
return uc, funcs, err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ 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/server"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"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"
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
|
@ -120,7 +120,12 @@ func (auth *Auth) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
|
|
||||||
//go:embed "login.html"
|
//go:embed "login.html"
|
||||||
var loginHTML []byte
|
var loginHTML []byte
|
||||||
var loginTemplate = templates.ParseForm("login.html", loginHTML, assets.AssetsUser)
|
var loginTemplate = templating.ParseForm(
|
||||||
|
"login.html", loginHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Login Required"),
|
||||||
|
templating.Assets(assets.AssetsUser),
|
||||||
|
)
|
||||||
|
|
||||||
var loginResponse = httpx.Response{
|
var loginResponse = httpx.Response{
|
||||||
ContentType: "text/plain",
|
ContentType: "text/plain",
|
||||||
|
|
@ -131,7 +136,15 @@ 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 {
|
||||||
tpl := loginTemplate.Prepare(auth.Dependencies.Templating)
|
tpl := loginTemplate.Prepare(
|
||||||
|
auth.Dependencies.Templating,
|
||||||
|
func(flags templating.Flags, r *http.Request) templating.Flags {
|
||||||
|
flags.Crumbs = []component.MenuItem{
|
||||||
|
{Title: "Login", Path: template.URL(r.URL.RequestURI())},
|
||||||
|
}
|
||||||
|
return flags
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return &httpx.Form[*AuthUser]{
|
return &httpx.Form[*AuthUser]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -141,16 +154,13 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
RenderTemplateContext: func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
if context.Err != nil {
|
if ctx.Err != nil {
|
||||||
context.Err = errLoginFailed
|
ctx.Err = errLoginFailed
|
||||||
}
|
}
|
||||||
tpl.Execute(w, r, templates.BaseFormContext{FormContext: context}, templates.BaseContextGaps{
|
return tpl.Context(r, templating.NewFormContext(ctx))
|
||||||
Crumbs: []component.MenuItem{
|
|
||||||
{Title: "Login", Path: template.URL(r.URL.RequestURI())},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
|
RenderTemplate: tpl.Template(),
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
||||||
username, password, passcode := values["username"], values["password"], values["otp"]
|
username, password, passcode := values["username"], values["password"], values["otp"]
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ type MenuItem struct {
|
||||||
Priority MenuPriority // menu priority
|
Priority MenuPriority // menu priority
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DummyMenuItem is a dummy menu item
|
||||||
|
// It should be replaced before being displayed to the user
|
||||||
|
var DummyMenuItem = MenuItem{
|
||||||
|
Title: "* to be replaced *",
|
||||||
|
}
|
||||||
|
|
||||||
func MenuItemSort(a, b MenuItem) bool {
|
func MenuItemSort(a, b MenuItem) bool {
|
||||||
return a.Priority < b.Priority
|
return a.Priority < b.Priority
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ import (
|
||||||
"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/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
|
@ -24,7 +25,7 @@ type Resolver struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
Instances *instances.Instances
|
Instances *instances.Instances
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
Auth *auth.Auth
|
Auth *auth.Auth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -50,36 +51,30 @@ func (resolver *Resolver) Routes() component.Routes {
|
||||||
|
|
||||||
//go:embed "resolver.html"
|
//go:embed "resolver.html"
|
||||||
var resolverHTML []byte
|
var resolverHTML []byte
|
||||||
var resolverTemplate = templates.Parse[resolverContext]("resolver.html", resolverHTML, assets.AssetsDefault)
|
var resolverTemplate = templating.Parse[resolverContext](
|
||||||
|
"resolver.html", resolverHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("Resolver"),
|
||||||
|
templating.Assets(assets.AssetsDefault),
|
||||||
|
)
|
||||||
|
|
||||||
type resolverContext struct {
|
type resolverContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
wdresolve.IndexContext
|
wdresolve.IndexContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
tpl := resolverTemplate.Prepare(resolver.Dependencies.Templating, templates.BaseContextGaps{
|
// get the resolver template
|
||||||
Crumbs: []component.MenuItem{
|
tpl := resolverTemplate.Prepare(
|
||||||
{Title: "Resolver", Path: "/wisski/get/"},
|
resolver.Dependencies.Templating,
|
||||||
},
|
templating.Crumbs(
|
||||||
})
|
component.MenuItem{Title: "Resolver", Path: "/wisski/get/"},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
t := tpl.Template()
|
||||||
|
|
||||||
|
// extract a logger and the fallback
|
||||||
logger := zerolog.Ctx(ctx)
|
logger := zerolog.Ctx(ctx)
|
||||||
|
|
||||||
var p wdresolve.ResolveHandler
|
|
||||||
var err error
|
|
||||||
|
|
||||||
p.HandleIndex = func(context wdresolve.IndexContext, w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx := resolverContext{
|
|
||||||
IndexContext: context,
|
|
||||||
}
|
|
||||||
if !resolver.Dependencies.Auth.Has(auth.User, r) {
|
|
||||||
ctx.IndexContext.Prefixes = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tpl.Execute(w, r, ctx)
|
|
||||||
}
|
|
||||||
p.TrustXForwardedProto = true
|
|
||||||
|
|
||||||
fallback := &resolvers.Regexp{
|
fallback := &resolvers.Regexp{
|
||||||
Data: map[string]string{},
|
Data: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
@ -97,12 +92,26 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
|
||||||
logger.Info().Str("name", domainName).Msg("registering legacy domain")
|
logger.Info().Str("name", domainName).Msg("registering legacy domain")
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolve the prefixes
|
p := wdresolve.ResolveHandler{
|
||||||
p.Resolver = resolvers.InOrder{
|
HandleIndex: func(context wdresolve.IndexContext, w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := resolverContext{
|
||||||
|
IndexContext: context,
|
||||||
|
}
|
||||||
|
if !resolver.Dependencies.Auth.Has(auth.User, r) {
|
||||||
|
ctx.IndexContext.Prefixes = nil
|
||||||
|
}
|
||||||
|
httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r)
|
||||||
|
},
|
||||||
|
|
||||||
|
Resolver: resolvers.InOrder{
|
||||||
resolver,
|
resolver,
|
||||||
fallback,
|
fallback,
|
||||||
|
},
|
||||||
|
|
||||||
|
TrustXForwardedProto: true,
|
||||||
}
|
}
|
||||||
return p, err
|
|
||||||
|
return p, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (resolver *Resolver) Target(uri string) string {
|
func (resolver *Resolver) Target(uri string) string {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}Resolver{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<p>
|
<p>
|
||||||
This page contains the global distillery resolver.
|
This page contains the global distillery resolver.
|
||||||
|
|
@ -62,4 +58,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/exporter/logger"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances/purger"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ type Admin struct {
|
||||||
|
|
||||||
Policy *policy.Policy
|
Policy *policy.Policy
|
||||||
|
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
|
|
||||||
Purger *purger.Purger
|
Purger *purger.Purger
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,75 +10,71 @@ 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/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"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/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "html/components.html"
|
//go:embed "html/anal.html"
|
||||||
var componentsHTML []byte
|
var analHTML []byte
|
||||||
var componentsTemplate = templates.Parse[componentContext]("components.html", componentsHTML, assets.AssetsAdmin)
|
var analTemplate = templating.Parse[analContext](
|
||||||
|
"anal.html", analHTML, nil,
|
||||||
|
|
||||||
type componentContext struct {
|
templating.Assets(assets.AssetsAdmin),
|
||||||
templates.BaseContext
|
)
|
||||||
|
|
||||||
|
type analContext struct {
|
||||||
|
templating.RuntimeFlags
|
||||||
|
|
||||||
Analytics lazy.PoolAnalytics
|
Analytics lazy.PoolAnalytics
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) components(ctx context.Context) http.Handler {
|
func (admin *Admin) components(ctx context.Context) http.Handler {
|
||||||
tpl := componentsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := analTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Components", Path: "/admin/components/"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
},
|
component.MenuItem{Title: "Components", Path: "/admin/components/"},
|
||||||
})
|
),
|
||||||
|
templating.Title("Components"),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandler(func(r *http.Request) (cp componentContext, err error) {
|
return tpl.HTMLHandler(func(r *http.Request) (ac analContext, err error) {
|
||||||
cp.Analytics = *admin.Analytics
|
ac.Analytics = *admin.Analytics
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "html/ingredients.html"
|
|
||||||
var ingredientsHTML []byte
|
|
||||||
var ingredientsTemplate = templates.Parse[ingredientsContext]("ingredients.html", ingredientsHTML, assets.AssetsAdmin)
|
|
||||||
|
|
||||||
type ingredientsContext struct {
|
|
||||||
templates.BaseContext
|
|
||||||
|
|
||||||
Instance models.Instance
|
|
||||||
Analytics *lazy.PoolAnalytics
|
|
||||||
}
|
|
||||||
|
|
||||||
func (admin *Admin) ingredients(ctx context.Context) http.Handler {
|
func (admin *Admin) ingredients(ctx context.Context) http.Handler {
|
||||||
tpl := ingredientsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := analTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Instance", Path: "* to be updated *"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Ingredients", Path: "* to be updated *"},
|
component.DummyMenuItem,
|
||||||
},
|
component.DummyMenuItem,
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *templates.BaseContextGaps) (ic ingredientsContext, err error) {
|
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ac analContext, funcs []templating.FlagFunc, err error) {
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
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 ic, httpx.ErrNotFound
|
return ac, nil, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ic, err
|
return ac, nil, err
|
||||||
|
}
|
||||||
|
funcs = []templating.FlagFunc{
|
||||||
|
templating.ReplaceCrumb(1, component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}),
|
||||||
|
templating.ReplaceCrumb(2, component.MenuItem{Title: "Ingredients", Path: template.URL("/admin/instance/" + slug + "/ingredients/")}),
|
||||||
|
templating.Title(instance.Name() + " - Ingredients"),
|
||||||
}
|
}
|
||||||
ic.Instance = instance.Instance
|
|
||||||
|
|
||||||
// and get the components
|
// and get the components
|
||||||
ic.Analytics = instance.Info().Analytics
|
ac.Analytics = *instance.Info().Analytics
|
||||||
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ 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/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
@ -22,10 +22,14 @@ import (
|
||||||
|
|
||||||
//go:embed "html/grants.html"
|
//go:embed "html/grants.html"
|
||||||
var grantsHTML []byte
|
var grantsHTML []byte
|
||||||
var grantsTemplate = templates.Parse[grantsContext]("grants.html", grantsHTML, assets.AssetsAdmin)
|
var grantsTemplate = templating.Parse[grantsContext](
|
||||||
|
"grants.html", grantsHTML, nil,
|
||||||
|
|
||||||
|
templating.Assets(assets.AssetsAdmin),
|
||||||
|
)
|
||||||
|
|
||||||
type grantsContext struct {
|
type grantsContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
Error string
|
Error string
|
||||||
|
|
||||||
|
|
@ -38,40 +42,43 @@ type grantsContext struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) grants(ctx context.Context) http.Handler {
|
func (admin *Admin) grants(ctx context.Context) http.Handler {
|
||||||
tpl := grantsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := grantsTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Instance", Path: "*to be updated*"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Grants", Path: "*to be updated*"},
|
component.DummyMenuItem,
|
||||||
},
|
component.DummyMenuItem,
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *templates.BaseContextGaps) (grantsContext, error) {
|
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (grantsContext, []templating.FlagFunc, error) {
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
return admin.getGrants(r, gaps)
|
return admin.getGrants(r)
|
||||||
} else {
|
} else {
|
||||||
return admin.postGrants(r, gaps)
|
return admin.postGrants(r)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) getGrants(r *http.Request, gaps *templates.BaseContextGaps) (gc grantsContext, err error) {
|
func (admin *Admin) getGrants(r *http.Request) (gc grantsContext, funcs []templating.FlagFunc, err error) {
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
||||||
if err := gc.use(r, gaps, slug, admin); err != nil {
|
|
||||||
return gc, err
|
funcs, err = gc.use(r, slug, admin)
|
||||||
|
if err != nil {
|
||||||
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gc.useGrants(r, admin); err != nil {
|
if err := gc.useGrants(r, admin); err != nil {
|
||||||
return gc, err
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return gc, nil
|
return gc, funcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) postGrants(r *http.Request, gaps *templates.BaseContextGaps) (gc grantsContext, err error) {
|
func (admin *Admin) postGrants(r *http.Request) (gc grantsContext, funcs []templating.FlagFunc, err error) {
|
||||||
// parse the form
|
// parse the form
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
return gc, err
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// read out the form values
|
// read out the form values
|
||||||
|
|
@ -84,15 +91,16 @@ func (admin *Admin) postGrants(r *http.Request, gaps *templates.BaseContextGaps)
|
||||||
)
|
)
|
||||||
|
|
||||||
// set the common fields
|
// set the common fields
|
||||||
if err := gc.use(r, gaps, slug, admin); err != nil {
|
funcs, err = gc.use(r, slug, admin)
|
||||||
return gc, err
|
if err != nil {
|
||||||
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if delete {
|
if delete {
|
||||||
// delete the user grant
|
// delete the user grant
|
||||||
err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug)
|
err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return gc, err
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// update the grant
|
// update the grant
|
||||||
|
|
@ -110,26 +118,29 @@ func (admin *Admin) postGrants(r *http.Request, gaps *templates.BaseContextGaps)
|
||||||
|
|
||||||
// fetch the grants for the instance
|
// fetch the grants for the instance
|
||||||
if err := gc.useGrants(r, admin); err != nil {
|
if err := gc.useGrants(r, admin); err != nil {
|
||||||
return gc, err
|
return gc, nil, err
|
||||||
}
|
}
|
||||||
return gc, nil
|
return gc, funcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *grantsContext) use(r *http.Request, gaps *templates.BaseContextGaps, slug string, admin *Admin) (err error) {
|
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (funcs []templating.FlagFunc, 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 {
|
||||||
return httpx.ErrNotFound
|
return nil, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
gc.Instance = gc.instance.Instance
|
gc.Instance = gc.instance.Instance
|
||||||
|
|
||||||
return nil
|
// replace the functions
|
||||||
|
funcs = []templating.FlagFunc{
|
||||||
|
templating.ReplaceCrumb(1, component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}),
|
||||||
|
templating.ReplaceCrumb(2, component.MenuItem{Title: "Grants", Path: template.URL("/admin/instance/" + slug + "/grants/")}),
|
||||||
|
templating.Title(gc.Instance.Slug + " - Grants"),
|
||||||
|
}
|
||||||
|
return funcs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (gc *grantsContext) useGrants(r *http.Request, admin *Admin) (err error) {
|
func (gc *grantsContext) useGrants(r *http.Request, admin *Admin) (err error) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}Components{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
{{ template "_anal.html" .Analytics }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}{{ .Instance.Slug }} - Grants{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
{{ $csrf := .CSRF }}
|
{{ $csrf := .CSRF }}
|
||||||
{{ $slug := .Instance.Slug }}
|
{{ $slug := .Instance.Slug }}
|
||||||
<div class="pure-u-1-1">
|
<div class="pure-u-1-1">
|
||||||
|
|
@ -124,4 +120,3 @@
|
||||||
<option value="{{ $drupal }}">
|
<option value="{{ $drupal }}">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</datalist>
|
</datalist>
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}Admin{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1-1">
|
<div class="pure-u-1-1">
|
||||||
<h2 id="overview">Distillery Configuration</h2>
|
<h2 id="overview">Distillery Configuration</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -257,4 +253,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}{{ .Instance.Slug }} - Ingredients{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
{{ template "_anal.html" .Analytics }}
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}{{ .Instance.Slug }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1-1">
|
<div class="pure-u-1-1">
|
||||||
<h2 id="overview">Info & Status</h2>
|
<h2 id="overview">Info & Status</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -485,4 +481,3 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,4 +1,2 @@
|
||||||
{{ template "_form.html" . }}
|
{{ template "form.html" . }}
|
||||||
{{ define "form/title" }}Create User{{ end }}
|
|
||||||
{{ define "form/button" }}Create{{ end }}
|
{{ define "form/button" }}Create{{ end }}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}Users{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<div class="padding">
|
<div class="padding">
|
||||||
<div class="overflow">
|
<div class="overflow">
|
||||||
|
|
@ -105,5 +100,3 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -9,7 +9,7 @@ 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/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
@ -80,27 +80,33 @@ func (admin *Admin) Fetch(flags component.FetcherFlags, target *status.Distiller
|
||||||
|
|
||||||
//go:embed "html/index.html"
|
//go:embed "html/index.html"
|
||||||
var indexHTML []byte
|
var indexHTML []byte
|
||||||
var indexTemplate = templates.Parse[indexContext]("index.html", indexHTML, assets.AssetsAdmin)
|
var indexTemplate = templating.Parse[indexContext](
|
||||||
|
"index.html", indexHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("Admin"),
|
||||||
|
templating.Assets(assets.AssetsAdmin),
|
||||||
|
)
|
||||||
|
|
||||||
type indexContext struct {
|
type indexContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
status.Distillery
|
status.Distillery
|
||||||
Instances []status.WissKI
|
Instances []status.WissKI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) index(ctx context.Context) http.Handler {
|
func (admin *Admin) index(ctx context.Context) http.Handler {
|
||||||
tpl := indexTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := indexTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
Actions: []component.MenuItem{
|
),
|
||||||
{Title: "Users", Path: "/admin/users/"},
|
templating.Actions(
|
||||||
{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
|
component.MenuItem{Title: "Users", Path: "/admin/users/"},
|
||||||
},
|
component.MenuItem{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *templates.BaseContextGaps) (idx indexContext, err error) {
|
return tpl.HTMLHandler(func(r *http.Request) (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
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ 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/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
@ -18,49 +18,57 @@ import (
|
||||||
|
|
||||||
//go:embed "html/instance.html"
|
//go:embed "html/instance.html"
|
||||||
var instanceHTML []byte
|
var instanceHTML []byte
|
||||||
var instanceTemplate = templates.Parse[instanceContext]("instance.html", instanceHTML, assets.AssetsAdmin)
|
var instanceTemplate = templating.Parse[instanceContext](
|
||||||
|
"instance.html", instanceHTML, nil,
|
||||||
|
|
||||||
|
templating.Assets(assets.AssetsAdmin),
|
||||||
|
)
|
||||||
|
|
||||||
type instanceContext struct {
|
type instanceContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
Instance models.Instance
|
Instance models.Instance
|
||||||
Info status.WissKI
|
Info status.WissKI
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) instance(ctx context.Context) http.Handler {
|
func (admin *Admin) instance(ctx context.Context) http.Handler {
|
||||||
tpl := instanceTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := instanceTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Instance", Path: "*to be replaced*"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
},
|
component.DummyMenuItem,
|
||||||
Actions: []component.MenuItem{
|
),
|
||||||
{Title: "Grants", Path: "*to be replaced*"},
|
templating.Actions(
|
||||||
{Title: "Ingredients", Path: "*to be replaced*", Priority: component.SmallButton},
|
component.DummyMenuItem,
|
||||||
},
|
component.DummyMenuItem,
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandlerWithGaps(func(r *http.Request, gaps *templates.BaseContextGaps) (ic instanceContext, err error) {
|
return tpl.HTMLHandlerWithFlags(func(r *http.Request) (ic instanceContext, funcs []templating.FlagFunc, err error) {
|
||||||
slug := httprouter.ParamsFromContext(r.Context()).ByName("slug")
|
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 ic, httpx.ErrNotFound
|
return ic, nil, httpx.ErrNotFound
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ic, err
|
return ic, nil, err
|
||||||
}
|
}
|
||||||
ic.Instance = instance.Instance
|
ic.Instance = instance.Instance
|
||||||
|
|
||||||
// get some more info about the wisski
|
// get some more info about the wisski
|
||||||
ic.Info, err = instance.Info().Information(r.Context(), false)
|
ic.Info, err = instance.Info().Information(r.Context(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ic, err
|
return ic, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
funcs = []templating.FlagFunc{
|
||||||
|
templating.ReplaceCrumb(1, component.MenuItem{Title: "Instance", Path: template.URL("/admin/instance/" + slug)}),
|
||||||
|
templating.ReplaceAction(0, component.MenuItem{Title: "Grants", Path: template.URL("/admin/grants/" + slug)}),
|
||||||
|
templating.ReplaceAction(1, component.MenuItem{Title: "Ingredients", Path: template.URL("/admin/ingredients/" + slug), Priority: component.SmallButton}),
|
||||||
|
|
||||||
|
templating.Title(instance.Name()),
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ 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/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"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"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
@ -19,25 +19,30 @@ import (
|
||||||
|
|
||||||
//go:embed "html/users.html"
|
//go:embed "html/users.html"
|
||||||
var usersHTML []byte
|
var usersHTML []byte
|
||||||
var usersTemplate = templates.Parse[usersContext]("user.html", usersHTML, assets.AssetsAdmin)
|
var usersTemplate = templating.Parse[usersContext](
|
||||||
|
"users.html", usersHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("Users"),
|
||||||
|
templating.Assets(assets.AssetsAdmin),
|
||||||
|
)
|
||||||
|
|
||||||
type usersContext struct {
|
type usersContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
Error string
|
Error string
|
||||||
Users []*auth.AuthUser
|
Users []*auth.AuthUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) users(ctx context.Context) http.Handler {
|
func (admin *Admin) users(ctx context.Context) http.Handler {
|
||||||
tpl := usersTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := usersTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Users", Path: "/admin/users/"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
},
|
component.MenuItem{Title: "Users", Path: "/admin/users/"},
|
||||||
Actions: []component.MenuItem{
|
),
|
||||||
{Title: "Create New", Path: "/admin/users/create/"},
|
templating.Actions(
|
||||||
},
|
component.MenuItem{Title: "Create New", Path: "/admin/users/create/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandler(func(r *http.Request) (uc usersContext, 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")
|
||||||
|
|
@ -48,7 +53,12 @@ func (admin *Admin) users(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
//go:embed "html/user_create.html"
|
//go:embed "html/user_create.html"
|
||||||
var userCreateHTML []byte
|
var userCreateHTML []byte
|
||||||
var userCreateTemplate = templates.ParseForm("user_create.html", userCreateHTML, assets.AssetsAdmin)
|
var userCreateTemplate = templating.ParseForm(
|
||||||
|
"user_create.html", userCreateHTML, httpx.FormTemplate,
|
||||||
|
|
||||||
|
templating.Title("Create User"),
|
||||||
|
templating.Assets(assets.AssetsAdmin),
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errCreateInvalidUsername = errors.New("invalid username")
|
errCreateInvalidUsername = errors.New("invalid username")
|
||||||
|
|
@ -62,13 +72,14 @@ type createUserResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
||||||
tpl := userCreateTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := userCreateTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
admin.Dependencies.Templating,
|
||||||
{Title: "Admin", Path: "/admin/"},
|
templating.Crumbs(
|
||||||
{Title: "Users", Path: "/admin/users"},
|
component.MenuItem{Title: "Admin", Path: "/admin/"},
|
||||||
{Title: "Create", Path: "/admin/users/create"},
|
component.MenuItem{Title: "Users", Path: "/admin/users"},
|
||||||
},
|
component.MenuItem{Title: "Create", Path: "/admin/users/create"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return &httpx.Form[createUserResult]{
|
return &httpx.Form[createUserResult]{
|
||||||
Fields: []field.Field{
|
Fields: []field.Field{
|
||||||
|
|
@ -79,7 +90,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
|
||||||
FieldTemplate: field.PureCSSFieldTemplate,
|
FieldTemplate: field.PureCSSFieldTemplate,
|
||||||
|
|
||||||
RenderTemplate: tpl.Template(),
|
RenderTemplate: tpl.Template(),
|
||||||
RenderTemplateContext: templates.FormTemplateContext(tpl),
|
RenderTemplateContext: templating.FormTemplateContext(tpl),
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -17,32 +17,8 @@ import (
|
||||||
//
|
//
|
||||||
// Each asset group should be registered as a parameter to the 'go:generate' line.
|
// Each asset group should be registered as a parameter to the 'go:generate' line.
|
||||||
type Assets struct {
|
type Assets struct {
|
||||||
Scripts string // <script> tags inserted by the asset
|
Scripts template.HTML // <script> tags inserted by the asset
|
||||||
Styles string // <link> tags inserted by the asset
|
Styles template.HTML // <link> tags inserted by the asset
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:generate node build.mjs Default User Admin
|
//go:generate node build.mjs Default User Admin
|
||||||
|
|
||||||
// MustParse parses a new template from the given source
|
|
||||||
// and calls [RegisterAssoc] on it.
|
|
||||||
func (assets *Assets) MustParse(t *template.Template, value string) *template.Template {
|
|
||||||
t = template.Must(t.Parse(value))
|
|
||||||
assets.RegisterAssoc(t)
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// MustParseShared is like [MustParse], but creates a new SharedTemplate instead
|
|
||||||
func (assets *Assets) MustParseShared(name string, value string) *template.Template {
|
|
||||||
return assets.MustParse(NewSharedTemplate(name), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RegisterAssoc registers two new associated templates with t.
|
|
||||||
//
|
|
||||||
// The template "scripts" will render all script tags required.
|
|
||||||
// The template "styles" will render all style tags required.
|
|
||||||
//
|
|
||||||
// If either template already exists, it will be overwritten.
|
|
||||||
func (assets *Assets) RegisterAssoc(t *template.Template) {
|
|
||||||
t.New("scripts").Parse(assets.Scripts)
|
|
||||||
t.New("styles").Parse(assets.Styles)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package assets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"html/template"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed "templates/*.html"
|
|
||||||
var templates embed.FS
|
|
||||||
|
|
||||||
var (
|
|
||||||
shared *template.Template = template.Must(template.ParseFS(templates, "templates/*.html"))
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewSharedTemplate creates a new template with the given name.
|
|
||||||
// It will be able to make use of shared templates as well as functions.
|
|
||||||
func NewSharedTemplate(name string) *template.Template {
|
|
||||||
new := template.New(name)
|
|
||||||
for _, template := range shared.Templates() {
|
|
||||||
new.AddParseTree(template.Tree.Name, template.Tree.Copy())
|
|
||||||
}
|
|
||||||
return new
|
|
||||||
}
|
|
||||||
5
internal/dis/component/server/home/about.html
Normal file
5
internal/dis/component/server/home/about.html
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div class="pure-u-1">
|
||||||
|
<p>
|
||||||
|
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
@ -7,7 +7,7 @@ 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/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||||
)
|
)
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
type Home struct {
|
type Home struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
Instances *instances.Instances
|
Instances *instances.Instances
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,41 +3,73 @@ package home
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"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/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "public.html"
|
//go:embed "public.html"
|
||||||
var publicHTML []byte
|
var publicHTML []byte
|
||||||
var publicTemplate = templates.Parse[publicContext]("public.html", publicHTML, assets.AssetsDefault)
|
var publicTemplate = templating.Parse[publicContext](
|
||||||
|
"public.html", publicHTML, nil,
|
||||||
|
|
||||||
type publicContext struct {
|
templating.Title("WissKI Distillery"),
|
||||||
templates.BaseContext
|
templating.Assets(assets.AssetsDefault),
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed "about.html"
|
||||||
|
var aboutHTML string
|
||||||
|
var aboutTemplate = template.Must(template.New("about.html").Parse(aboutHTML))
|
||||||
|
|
||||||
|
// aboutContext is passed to about.html
|
||||||
|
type aboutContext struct {
|
||||||
Instances []status.WissKI
|
Instances []status.WissKI
|
||||||
SelfRedirect string
|
SelfRedirect string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// publicCOntext is passed to public.html
|
||||||
|
type publicContext struct {
|
||||||
|
templating.RuntimeFlags
|
||||||
|
|
||||||
|
aboutContext
|
||||||
|
About template.HTML
|
||||||
|
}
|
||||||
|
|
||||||
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
tpl := publicTemplate.Prepare(home.Dependencies.Templating, templates.BaseContextGaps{
|
|
||||||
Crumbs: []component.MenuItem{
|
tpl := publicTemplate.Prepare(
|
||||||
{Title: "WissKI Distillery", Path: "/"},
|
home.Dependencies.Templating,
|
||||||
},
|
templating.Crumbs(
|
||||||
})
|
component.MenuItem{Title: "WissKI Distillery", Path: "/"},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
about := home.Dependencies.Templating.GetCustomizable(aboutTemplate)
|
||||||
|
|
||||||
return tpl.HTMLHandler(func(r *http.Request) (pc publicContext, err error) {
|
return tpl.HTMLHandler(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
|
||||||
}
|
}
|
||||||
|
|
||||||
pc.Instances = home.homeInstances.Get(nil)
|
// prepare about
|
||||||
pc.SelfRedirect = home.Config.SelfRedirect.String()
|
pc.aboutContext.Instances = home.homeInstances.Get(nil)
|
||||||
|
pc.aboutContext.SelfRedirect = home.Config.SelfRedirect.String()
|
||||||
|
|
||||||
|
// render the about template
|
||||||
|
var builder strings.Builder
|
||||||
|
if err := about.Execute(&builder, pc.aboutContext); err != nil {
|
||||||
|
return pc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// and return about!
|
||||||
|
pc.About = template.HTML(builder.String())
|
||||||
|
|
||||||
return
|
return
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,4 @@
|
||||||
{{ template "_base.html" . }}
|
{{ .About }}
|
||||||
{{ define "title" }}WissKI Distillery{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
{{ block "@custom/about" . }}
|
|
||||||
<div class="pure-u-1">
|
|
||||||
<p>
|
|
||||||
For more information, see <a href="{{ .SelfRedirect }}">{{ .SelfRedirect }}</a>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<h2>WissKIs on this Distillery</h2>
|
<h2>WissKIs on this Distillery</h2>
|
||||||
|
|
@ -33,4 +23,3 @@
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ 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/server"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
@ -17,7 +17,7 @@ type Legal struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
Static *assets.Static
|
Static *assets.Static
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -27,10 +27,15 @@ var (
|
||||||
|
|
||||||
//go:embed "legal.html"
|
//go:embed "legal.html"
|
||||||
var legalHTML []byte
|
var legalHTML []byte
|
||||||
var legalTemplate = templates.Parse[legalContext]("legal.html", legalHTML, assets.AssetsDefault)
|
var legalTemplate = templating.Parse[legalContext](
|
||||||
|
"legal.html", legalHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("Legal"),
|
||||||
|
templating.Assets(assets.AssetsDefault),
|
||||||
|
)
|
||||||
|
|
||||||
type legalContext struct {
|
type legalContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
|
|
||||||
LegalNotices string
|
LegalNotices string
|
||||||
|
|
||||||
|
|
@ -49,11 +54,12 @@ func (legal *Legal) Routes() component.Routes {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
func (legal *Legal) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
tpl := legalTemplate.Prepare(legal.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := legalTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
legal.Dependencies.Templating,
|
||||||
{Title: "Legal", Path: "/legal/"},
|
templating.Crumbs(
|
||||||
},
|
component.MenuItem{Title: "Legal", Path: "/legal/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
return tpl.HTMLHandler(func(r *http.Request) (lc legalContext, err error) {
|
return tpl.HTMLHandler(func(r *http.Request) (lc legalContext, err error) {
|
||||||
lc.LegalNotices = cli.LegalNotices
|
lc.LegalNotices = cli.LegalNotices
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}Legal{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
<h2 id="cookies">Cookie Usage</h2>
|
<h2 id="cookies">Cookie Usage</h2>
|
||||||
|
|
||||||
|
|
@ -69,4 +65,3 @@
|
||||||
|
|
||||||
<pre>{{ .AssetsDisclaimer }}</pre>
|
<pre>{{ .AssetsDisclaimer }}</pre>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
|
||||||
|
|
@ -11,7 +11,7 @@ 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/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"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"
|
||||||
|
|
@ -22,7 +22,7 @@ import (
|
||||||
type News struct {
|
type News struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,20 +113,26 @@ func Items() ([]Item, error) {
|
||||||
|
|
||||||
//go:embed "news.html"
|
//go:embed "news.html"
|
||||||
var newsHTML []byte
|
var newsHTML []byte
|
||||||
var newsTemplate = templates.Parse[newsContext]("news.html", newsHTML, assets.AssetsDefault)
|
var newsTemplate = templating.Parse[newsContext](
|
||||||
|
"news.html", newsHTML, nil,
|
||||||
|
|
||||||
|
templating.Title("News"),
|
||||||
|
templating.Assets(assets.AssetsDefault),
|
||||||
|
)
|
||||||
|
|
||||||
type newsContext struct {
|
type newsContext struct {
|
||||||
templates.BaseContext
|
templating.RuntimeFlags
|
||||||
Items []Item
|
Items []Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
tpl := newsTemplate.Prepare(news.Dependencies.Templating, templates.BaseContextGaps{
|
tpl := newsTemplate.Prepare(
|
||||||
Crumbs: []component.MenuItem{
|
news.Dependencies.Templating,
|
||||||
{Title: "News", Path: "/news/"},
|
templating.Crumbs(
|
||||||
},
|
component.MenuItem{Title: "News", Path: "/news/"},
|
||||||
})
|
),
|
||||||
|
)
|
||||||
|
|
||||||
items, itemsErr := Items()
|
items, itemsErr := Items()
|
||||||
if itemsErr != nil {
|
if itemsErr != nil {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}News{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
This page contains news items from the distillery.
|
This page contains news items from the distillery.
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,5 +9,3 @@
|
||||||
{{ .Content }}
|
{{ .Content }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"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/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
|
"github.com/FAU-CDI/wisski-distillery/pkg/cancel"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/mux"
|
"github.com/FAU-CDI/wisski-distillery/pkg/mux"
|
||||||
|
|
@ -22,7 +22,7 @@ type Server struct {
|
||||||
Routeables []component.Routeable
|
Routeables []component.Routeable
|
||||||
Cronables []component.Cronable
|
Cronables []component.Cronable
|
||||||
|
|
||||||
Templating *templates.Templating
|
Templating *templating.Templating
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
|
||||||
"github.com/gorilla/csrf"
|
|
||||||
"github.com/tkw1536/goprogram/lib/reflectx"
|
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
|
||||||
|
|
||||||
// baseContextName is the name of the [BaseContext] type
|
|
||||||
var baseContextName = reflectx.TypeOf[BaseContext]().Name()
|
|
||||||
|
|
||||||
// BaseContext represents a context used by templates
|
|
||||||
//
|
|
||||||
// Other invocations might cause an error at runtime.
|
|
||||||
type BaseContext struct {
|
|
||||||
inited bool // has this context been inited?
|
|
||||||
requestWasNil bool // was the passed request nil
|
|
||||||
|
|
||||||
GeneratedAt time.Time // time this page was generated at
|
|
||||||
|
|
||||||
// Menu and breadcrumbs
|
|
||||||
Menu []component.MenuItem
|
|
||||||
BaseContextGaps
|
|
||||||
|
|
||||||
CSRF template.HTML // CSRF Field
|
|
||||||
}
|
|
||||||
|
|
||||||
// constants that are used in various parts of the template to render stuff
|
|
||||||
const (
|
|
||||||
errorPrefix template.HTML = `<div style="z-index:10000;position:fixed;top:0;left:0;width:100vh;height:100vw;background:red;text-align:center;padding:10vh 10vw;font-size:xx-large;font-weight:bold">`
|
|
||||||
errorSuffix template.HTML = "</div>"
|
|
||||||
|
|
||||||
csrfError template.HTML = errorPrefix + "CSRF used but not provided" + errorSuffix
|
|
||||||
initError template.HTML = errorPrefix + "<code>BaseContext.use()</code> not called" + errorSuffix
|
|
||||||
requestNilError template.HTML = errorPrefix + "<code>BaseContext.use()</code> called with nil request" + errorSuffix
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaseContextGaps struct {
|
|
||||||
Crumbs []component.MenuItem
|
|
||||||
Actions []component.MenuItem
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bcg BaseContextGaps) clone() BaseContextGaps {
|
|
||||||
return BaseContextGaps{
|
|
||||||
Crumbs: slices.Clone(bcg.Crumbs),
|
|
||||||
Actions: slices.Clone(bcg.Actions),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update updates an embedded BaseContext field in context.
|
|
||||||
func (tpl *Templating) update(context any, r *http.Request, bcg BaseContextGaps) *BaseContext {
|
|
||||||
tc := reflect.ValueOf(context).
|
|
||||||
Elem().FieldByName(baseContextName).Addr().
|
|
||||||
Interface().(*BaseContext)
|
|
||||||
|
|
||||||
tc.inited = true
|
|
||||||
tc.requestWasNil = r == nil
|
|
||||||
|
|
||||||
tc.GeneratedAt = time.Now().UTC()
|
|
||||||
|
|
||||||
// setup the CSRF field
|
|
||||||
tc.CSRF = csrfError
|
|
||||||
if r != nil {
|
|
||||||
tc.CSRF = csrf.TemplateField(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// build the menu
|
|
||||||
tc.Menu = tpl.buildMenu(r)
|
|
||||||
|
|
||||||
// build the breadcrumbs
|
|
||||||
tc.BaseContextGaps = bcg.clone()
|
|
||||||
last := len(tc.Crumbs) - 1
|
|
||||||
for i := range tc.Crumbs {
|
|
||||||
tc.Crumbs[i].Active = i == last
|
|
||||||
}
|
|
||||||
|
|
||||||
return tc
|
|
||||||
}
|
|
||||||
|
|
||||||
// DoInitCheck is called by the template to check that the BaseContext was initialized properly
|
|
||||||
func (bc BaseContext) DoInitCheck() template.HTML {
|
|
||||||
if !bc.inited {
|
|
||||||
return initError
|
|
||||||
}
|
|
||||||
if bc.requestWasNil {
|
|
||||||
return requestNilError
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// BaseFormContext combines BaseContext and FormContext
|
|
||||||
type BaseFormContext struct {
|
|
||||||
BaseContext
|
|
||||||
httpx.FormContext
|
|
||||||
}
|
|
||||||
|
|
@ -1,139 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
|
||||||
"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 assets.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(tpl *Templating, gaps ...BaseContextGaps) *Template[C] {
|
|
||||||
wrap := Template[C]{
|
|
||||||
tpl: tpl,
|
|
||||||
template: tpl.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 {
|
|
||||||
tpl *Templating
|
|
||||||
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.tpl.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)
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "embed"
|
|
||||||
"html/template"
|
|
||||||
"text/template/parse"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
footerName = "@custom/footer"
|
|
||||||
aboutName = "@custom/about"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed "footer.html"
|
|
||||||
var footerTemplateStr string
|
|
||||||
var defaultFooterTemplate = template.Must(template.New("footer.html").Parse(footerTemplateStr))
|
|
||||||
|
|
||||||
// Template creates a copy of template with shared template parts updated accordingly.
|
|
||||||
// Any template using this should use one of the template contexts in this package.
|
|
||||||
func (tpl *Templating) Template(t *template.Template) *template.Template {
|
|
||||||
// TODO: This should not be used!
|
|
||||||
|
|
||||||
// create a clone of the template
|
|
||||||
clone := template.Must(t.Clone())
|
|
||||||
|
|
||||||
// add all the fixed parse trees
|
|
||||||
footerTree := tpl.getTemplateAsset(defaultFooterTemplate)
|
|
||||||
template.Must(clone.AddParseTree(footerName, footerTree))
|
|
||||||
|
|
||||||
// optionally add the about asset
|
|
||||||
if aboutTree := tpl.readTemplateAsset("about.html"); clone.Lookup(aboutName) != nil && aboutTree != nil {
|
|
||||||
template.Must(clone.AddParseTree(aboutName, aboutTree))
|
|
||||||
}
|
|
||||||
return clone // and return the tree
|
|
||||||
}
|
|
||||||
|
|
||||||
// getTemplateAsset returns an overridable template asset.
|
|
||||||
//
|
|
||||||
// If the asset named can successfully be parsed, it is returned.
|
|
||||||
// If it can not be parsed, the default template is returned.
|
|
||||||
func (tpl *Templating) getTemplateAsset(dflt *template.Template) *parse.Tree {
|
|
||||||
tree := tpl.readTemplateAsset(dflt.Name())
|
|
||||||
if tree == nil {
|
|
||||||
return dflt.Tree.Copy()
|
|
||||||
}
|
|
||||||
return tree
|
|
||||||
}
|
|
||||||
|
|
||||||
// readTemplateAsset is like getTemplateAssets, but takes an explicit name to read.
|
|
||||||
// when the asset does not exist, or cannot be opened, returns nil.
|
|
||||||
func (tpl *Templating) readTemplateAsset(name string) *parse.Tree {
|
|
||||||
template, err := (func() (*template.Template, error) {
|
|
||||||
data, err := environment.ReadFile(tpl.Environment, tpl.CustomAssetPath(name))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return template.New(name).Parse(string(data))
|
|
||||||
})()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return template.Tree
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package templates
|
package templating
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
193
internal/dis/component/server/templating/base.go
Normal file
193
internal/dis/component/server/templating/base.go
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
package templating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed "src/base.html"
|
||||||
|
var baseHTML string
|
||||||
|
var baseTemplate = template.Must(template.New("base.html").Parse(baseHTML))
|
||||||
|
|
||||||
|
// Tempalte represents an executable template.
|
||||||
|
type Template[C any] struct {
|
||||||
|
templating *Templating
|
||||||
|
p *Parsed[C]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template returns a template that, if executed together with the context by the Context method, produces the desired result.
|
||||||
|
func (tpl *Template[C]) Template() *template.Template {
|
||||||
|
return baseTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context generates the context to pass to an instance of the template returned by Template.
|
||||||
|
func (tpl *Template[C]) Context(r *http.Request, c C, funcs ...FlagFunc) (ctx *tContext[C]) {
|
||||||
|
// create a new context
|
||||||
|
ctx = new(tContext[C])
|
||||||
|
|
||||||
|
// setup the basic properties
|
||||||
|
ctx.ctx = r.Context()
|
||||||
|
ctx.Runtime.RequestURI = r.URL.RequestURI()
|
||||||
|
ctx.Runtime.GeneratedAt = time.Now().UTC()
|
||||||
|
ctx.Runtime.CSRF = csrf.TemplateField(r)
|
||||||
|
ctx.Runtime.Menu = tpl.templating.buildMenu(r)
|
||||||
|
|
||||||
|
// generate the rest of the options
|
||||||
|
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, tpl.p.funcs...)
|
||||||
|
ctx.Runtime.Flags = ctx.Runtime.Flags.Apply(r, funcs...)
|
||||||
|
|
||||||
|
// if the context has a runtime flags embed, then set the field properly
|
||||||
|
if tpl.p.hasRuntimeFlagsEmbed {
|
||||||
|
reflect.ValueOf(&c).Elem().
|
||||||
|
FieldByName(runtimeFlagsName).
|
||||||
|
Set(reflect.ValueOf(ctx.Runtime))
|
||||||
|
}
|
||||||
|
|
||||||
|
// the main template
|
||||||
|
ctx.cMain = c
|
||||||
|
ctx.tMain = tpl.p.tpl
|
||||||
|
|
||||||
|
// the footer template
|
||||||
|
ctx.tFooter = tpl.templating.GetCustomizable(footerTemplate)
|
||||||
|
ctx.cFooter = ctx.Runtime
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseForm is like Parse[BaseFormContext]
|
||||||
|
var ParseForm = Parse[FormContext]
|
||||||
|
|
||||||
|
type FormContext struct {
|
||||||
|
httpx.FormContext
|
||||||
|
RuntimeFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormContext returns a new FormContext from an underlying context
|
||||||
|
func NewFormContext(context httpx.FormContext) FormContext {
|
||||||
|
return FormContext{FormContext: context}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormTemplateContext returns a new handler for a form with the given base context
|
||||||
|
func FormTemplateContext(tw *Template[FormContext]) func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
// TODO: Is this needed?
|
||||||
|
return func(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
return tw.Context(r, FormContext{FormContext: ctx})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.HandlerWithFlags(func(r *http.Request) (C, []FlagFunc, error) {
|
||||||
|
c, err := f(r)
|
||||||
|
return c, nil, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerWithFlags works like handler, but additionally receive funcs to generate flags
|
||||||
|
func (tw *Template[C]) HandlerWithFlags(f func(r *http.Request) (C, []FlagFunc, error)) func(r *http.Request) (any, error) {
|
||||||
|
return func(r *http.Request) (any, error) {
|
||||||
|
c, funcs, err := f(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tw.Context(r, c, funcs...), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tw *Template[C]) HTMLHandlerWithFlags(f func(r *http.Request) (C, []FlagFunc, error)) httpx.HTMLHandler[any] {
|
||||||
|
return httpx.HTMLHandler[any]{
|
||||||
|
Handler: tw.HandlerWithFlags(f),
|
||||||
|
Template: tw.Template(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tContext is passed to the underlying template.
|
||||||
|
//
|
||||||
|
// Callers may not retain references beyond the invocation of the template.
|
||||||
|
// Callers must not rely on the internal structure of this tContext.
|
||||||
|
type tContext[C any] struct {
|
||||||
|
Runtime RuntimeFlags // underlying flags
|
||||||
|
|
||||||
|
ctx context.Context // underlying context for render
|
||||||
|
|
||||||
|
// the main template and context
|
||||||
|
tMain *template.Template
|
||||||
|
cMain C
|
||||||
|
|
||||||
|
// the footer template and context
|
||||||
|
tFooter *template.Template
|
||||||
|
cFooter RuntimeFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main renders the main template.
|
||||||
|
func (ctx *tContext[C]) Main() (template.HTML, error) {
|
||||||
|
return ctx.renderSafe("main", ctx.tMain, ctx.cMain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer renders the footer template
|
||||||
|
func (ctx *tContext[C]) Footer() (template.HTML, error) {
|
||||||
|
return ctx.renderSafe("footer", ctx.tFooter, ctx.cFooter)
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderSafeError = "Error displaying page. See server log for details. "
|
||||||
|
|
||||||
|
func (ctx *tContext[C]) renderSafe(name string, t *template.Template, c any) (template.HTML, error) {
|
||||||
|
|
||||||
|
// already done
|
||||||
|
if err := ctx.ctx.Err(); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err, panicked := func() (value template.HTML, err error, panicked bool) {
|
||||||
|
var builder strings.Builder
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if panicked {
|
||||||
|
r := recover()
|
||||||
|
zerolog.Ctx(ctx.ctx).Error().
|
||||||
|
Str("uri", ctx.Runtime.RequestURI).
|
||||||
|
Str("name", name).
|
||||||
|
Str("panic", fmt.Sprint(r)).
|
||||||
|
Msg("templating.Main(): template panic()ed")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
panicked = true
|
||||||
|
err = t.Execute(&builder, c)
|
||||||
|
panicked = false
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zerolog.Ctx(ctx.ctx).Err(err).
|
||||||
|
Str("uri", ctx.Runtime.RequestURI).
|
||||||
|
Str("name", name).
|
||||||
|
Msg("template errored")
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.HTML(builder.String()), err, false
|
||||||
|
}()
|
||||||
|
|
||||||
|
if err != nil || panicked {
|
||||||
|
return renderSafeError, httpx.ErrInternalServerError
|
||||||
|
}
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
101
internal/dis/component/server/templating/flags.go
Normal file
101
internal/dis/component/server/templating/flags.go
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
package templating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
|
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Flags represent handle-updatable options for the base template
|
||||||
|
type Flags struct {
|
||||||
|
Title string // Title of the menu
|
||||||
|
assets.Assets // assets are the assets included in the template
|
||||||
|
|
||||||
|
Crumbs []component.MenuItem // crumbs are the breadcrumbs leading to a specific action
|
||||||
|
Actions []component.MenuItem // actions are the actions available to a specific thingy
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply applies a set of functions to this flags
|
||||||
|
func (flags Flags) Apply(r *http.Request, funcs ...FlagFunc) Flags {
|
||||||
|
for _, f := range funcs {
|
||||||
|
flags = f(flags, r)
|
||||||
|
}
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeFlags are passed to the template at runtime.
|
||||||
|
// Any context may e
|
||||||
|
type RuntimeFlags struct {
|
||||||
|
Flags
|
||||||
|
|
||||||
|
RequestURI string // request uri of the current page
|
||||||
|
Menu []component.MenuItem // menu at the top of the page
|
||||||
|
GeneratedAt time.Time // time the underlying data returned
|
||||||
|
CSRF template.HTML // csrf data (if any)
|
||||||
|
}
|
||||||
|
|
||||||
|
var runtimeFlagsName = reflectx.TypeOf[RuntimeFlags]().Name()
|
||||||
|
|
||||||
|
// Clone clones this flags
|
||||||
|
func (flags Flags) Clone() Flags {
|
||||||
|
flags.Crumbs = slices.Clone(flags.Crumbs)
|
||||||
|
flags.Actions = slices.Clone(flags.Actions)
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
|
||||||
|
// FlagFunc updates a flags based on a request.
|
||||||
|
// FlagFunc may not be nil.
|
||||||
|
type FlagFunc func(flags Flags, r *http.Request) Flags
|
||||||
|
|
||||||
|
// Assets sets the given assets for the given flags
|
||||||
|
func Assets(Assets assets.Assets) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Assets = Assets
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crumbs sets the crumbs
|
||||||
|
func Crumbs(crumbs ...component.MenuItem) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Crumbs = crumbs
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions sets the actions
|
||||||
|
func Actions(actions ...component.MenuItem) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Actions = actions
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceAction replaces a specific action
|
||||||
|
func ReplaceAction(index int, action component.MenuItem) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Actions[index] = action
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceCrumb replaces a specific crum
|
||||||
|
func ReplaceCrumb(index int, action component.MenuItem) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Crumbs[index] = action
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title sets the title of this template
|
||||||
|
func Title(title string) FlagFunc {
|
||||||
|
return func(flags Flags, r *http.Request) Flags {
|
||||||
|
flags.Title = title
|
||||||
|
return flags
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package templates
|
package templating
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
"html/template"
|
||||||
66
internal/dis/component/server/templating/parse.go
Normal file
66
internal/dis/component/server/templating/parse.go
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
package templating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parsed represents a parsed template that takes as argument a context of type C.
|
||||||
|
type Parsed[C any] struct {
|
||||||
|
// does the context type an embed of the runtime flags type?
|
||||||
|
hasRuntimeFlagsEmbed bool
|
||||||
|
|
||||||
|
tpl *template.Template // parsed template
|
||||||
|
funcs []FlagFunc // optionally concfigured functions.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse parses a template with the given name and source.
|
||||||
|
// If base is not nil, every template associated with the base template is copied into the given template.
|
||||||
|
// Functions will be applied on creation time to represent the context for the given template.
|
||||||
|
func Parse[C any](name string, source []byte, base *template.Template, funcs ...FlagFunc) Parsed[C] {
|
||||||
|
tp := reflectx.TypeOf[C]()
|
||||||
|
|
||||||
|
// determine if we have an embedded field in the struct
|
||||||
|
var hasEmbed bool
|
||||||
|
if tp.Kind() == reflect.Struct {
|
||||||
|
field, ok := tp.FieldByName(runtimeFlagsName)
|
||||||
|
if ok {
|
||||||
|
hasEmbed = field.Anonymous
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new template, and optionally inherit from the base template
|
||||||
|
new := template.New(name)
|
||||||
|
if base != nil {
|
||||||
|
for _, tree := range base.Templates() {
|
||||||
|
root := tree.Tree.Copy()
|
||||||
|
new.AddParseTree(tree.Name(), root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Parsed[C]{
|
||||||
|
hasRuntimeFlagsEmbed: hasEmbed,
|
||||||
|
tpl: template.Must(new.Parse(string(source))),
|
||||||
|
funcs: funcs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare prepares this template to be used with the given templating.
|
||||||
|
func (p *Parsed[C]) Prepare(templating *Templating, funcs ...FlagFunc) *Template[C] {
|
||||||
|
pcopy := *p // make a copy of p!
|
||||||
|
|
||||||
|
wrap := Template[C]{
|
||||||
|
templating: templating,
|
||||||
|
|
||||||
|
p: &pcopy,
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy the functions!
|
||||||
|
pcopy.funcs = slices.Clone(pcopy.funcs)
|
||||||
|
pcopy.funcs = append(wrap.p.funcs, funcs...)
|
||||||
|
|
||||||
|
return &wrap
|
||||||
|
}
|
||||||
|
|
@ -4,15 +4,14 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
|
|
||||||
<title>{{ block "title" . }}WissKI Distillery{{ end }}</title>
|
<title>{{ .Runtime.Flags.Title }}</title>
|
||||||
{{ block "styles" . }}styles{{ end }}
|
{{ .Runtime.Flags.Assets.Styles }}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{{ .BaseContext.DoInitCheck }}
|
|
||||||
<nav class="pure-menu pure-menu-horizontal">
|
<nav class="pure-menu pure-menu-horizontal">
|
||||||
<ul class="pure-menu-list" role="menubar">
|
<ul class="pure-menu-list" role="menubar">
|
||||||
{{ range .BaseContext.Menu }}
|
{{ range .Runtime.Menu }}
|
||||||
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}">
|
<li class="pure-menu-item{{ if .Active }} pure-menu-selected{{ end }}">
|
||||||
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
|
<a href="{{ .Path }}" class="pure-menu-link">{{ .Title }}</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
@ -20,16 +19,16 @@
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<nav class="breadcrumbs" role="navigation" aria-label="Breadcrumbs">
|
<nav class="breadcrumbs" role="navigation" aria-label="Breadcrumbs">
|
||||||
{{ range .BaseContext.Crumbs }}
|
{{ range .Runtime.Flags.Crumbs }}
|
||||||
<a class="{{ if .Active }}active{{ end }}" href="{{ .Path }}">{{ .Title }}</a>
|
<a class="{{ if .Active }}active{{ end }}" href="{{ .Path }}">{{ .Title }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<header>
|
<header>
|
||||||
<h1 id="top">{{ template "title" . }}</h1>
|
<h1 id="top">{{ .Runtime.Flags.Title }}</h1>
|
||||||
{{ if .BaseContext.Actions }}
|
{{ if .Runtime.Flags.Actions }}
|
||||||
<div class="pure-button-group" role="group" aria-label="Actions">
|
<div class="pure-button-group" role="group" aria-label="Actions">
|
||||||
{{ range .BaseContext.Actions }}
|
{{ range .Runtime.Flags.Actions }}
|
||||||
<a href="{{ .Path }}" class="pure-button{{ if eq .Priority -1 }} pure-button-small{{end}}">{{ .Title }}</a>
|
<a href="{{ .Path }}" class="pure-button{{ if eq .Priority -1 }} pure-button-small{{end}}">{{ .Title }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -37,20 +36,16 @@
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<div class="pure-g">
|
<div class="pure-g">
|
||||||
{{ block "content" . }}content{{ end }}
|
{{ .Main }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
{{ block "@custom/footer" .BaseContext }}
|
{{ .Footer }}
|
||||||
<div style="z-index:10000;position:fixed;top:0;left:0;width:100vh;height:100vw;background:red;text-align:center;padding:10vh 10vw;font-size:xx-large;font-weight:bold">
|
|
||||||
<code>.Templating.Template()</code> not called
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|
||||||
{{ block "scripts" . }}scripts{{ end }}
|
{{ .Runtime.Flags.Assets.Scripts }}
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
29
internal/dis/component/server/templating/template.go
Normal file
29
internal/dis/component/server/templating/template.go
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
package templating
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/environment"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed "src/footer.html"
|
||||||
|
var footerHTML string
|
||||||
|
var footerTemplate = template.Must(template.New("footer.html").Parse(footerHTML))
|
||||||
|
|
||||||
|
// GetCustomizable returns either a clone of dflt, or the overriden template with the same name.
|
||||||
|
func (tpl *Templating) GetCustomizable(dflt *template.Template) *template.Template {
|
||||||
|
name := dflt.Name()
|
||||||
|
|
||||||
|
custom, err := (func() (*template.Template, error) {
|
||||||
|
data, err := environment.ReadFile(tpl.Environment, tpl.CustomAssetPath(name))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return template.New(name).Parse(string(data))
|
||||||
|
})()
|
||||||
|
if err != nil {
|
||||||
|
return template.Must(dflt.Clone())
|
||||||
|
}
|
||||||
|
return custom
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package templates
|
package templating
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/legal"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/legal"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/news"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/news"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/solr"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/solr"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/sql"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2"
|
||||||
|
|
@ -116,8 +116,8 @@ func (dis *Distillery) Info() *admin.Admin {
|
||||||
func (dis *Distillery) Policy() *policy.Policy {
|
func (dis *Distillery) Policy() *policy.Policy {
|
||||||
return export[*policy.Policy](dis)
|
return export[*policy.Policy](dis)
|
||||||
}
|
}
|
||||||
func (dis *Distillery) Templating() *templates.Templating {
|
func (dis *Distillery) Templating() *templating.Templating {
|
||||||
return export[*templates.Templating](dis)
|
return export[*templating.Templating](dis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dis *Distillery) Purger() *purger.Purger {
|
func (dis *Distillery) Purger() *purger.Purger {
|
||||||
|
|
@ -190,7 +190,7 @@ func (dis *Distillery) allComponents() []initFunc {
|
||||||
auto[*news.News],
|
auto[*news.News],
|
||||||
|
|
||||||
auto[*assets.Static],
|
auto[*assets.Static],
|
||||||
auto[*templates.Templating],
|
auto[*templating.Templating],
|
||||||
|
|
||||||
// Cron
|
// Cron
|
||||||
auto[*cron.Cron],
|
auto[*cron.Cron],
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ func StatusInterceptor(contentType string, body func(code int, text string) ([]b
|
||||||
|
|
||||||
return ErrInterceptor{
|
return ErrInterceptor{
|
||||||
Errors: map[error]Response{
|
Errors: map[error]Response{
|
||||||
|
ErrInternalServerError: makeResponse(http.StatusInternalServerError),
|
||||||
ErrBadRequest: makeResponse(http.StatusBadRequest),
|
ErrBadRequest: makeResponse(http.StatusBadRequest),
|
||||||
ErrNotFound: makeResponse(http.StatusNotFound),
|
ErrNotFound: makeResponse(http.StatusNotFound),
|
||||||
ErrForbidden: makeResponse(http.StatusForbidden),
|
ErrForbidden: makeResponse(http.StatusForbidden),
|
||||||
|
|
@ -60,6 +61,7 @@ func StatusInterceptor(contentType string, body func(code int, text string) ([]b
|
||||||
|
|
||||||
// Common errors accepted by all httpx handlers
|
// Common errors accepted by all httpx handlers
|
||||||
var (
|
var (
|
||||||
|
ErrInternalServerError = errors.New("httpx: Internal Server Error")
|
||||||
ErrBadRequest = errors.New("httpx: Bad Request")
|
ErrBadRequest = errors.New("httpx: Bad Request")
|
||||||
ErrNotFound = errors.New("httpx: Not Found")
|
ErrNotFound = errors.New("httpx: Not Found")
|
||||||
ErrForbidden = errors.New("httpx: Forbidden")
|
ErrForbidden = errors.New("httpx: Forbidden")
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx/field"
|
||||||
"github.com/gorilla/csrf"
|
"github.com/gorilla/csrf"
|
||||||
)
|
)
|
||||||
|
|
@ -173,3 +175,9 @@ func (form *Form[D]) renderSuccess(data D, values map[string]string, w http.Resp
|
||||||
}
|
}
|
||||||
form.renderForm(err, values, w, r)
|
form.renderForm(err, values, w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed "form.html"
|
||||||
|
var formBytes []byte
|
||||||
|
|
||||||
|
// FormTeplate is a template to embed a form
|
||||||
|
var FormTemplate = template.Must(template.New("form.html").Parse(string(formBytes)))
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,8 @@
|
||||||
{{ template "_base.html" . }}
|
|
||||||
{{ define "title" }}{{ block "form/title" . }}Form{{ end }}{{ end }}
|
|
||||||
|
|
||||||
{{ define "content" }}
|
|
||||||
<div class="pure-u-1">
|
<div class="pure-u-1">
|
||||||
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
{{ block "form/extra" . }}<!-- no extra -->{{ end }}
|
||||||
|
|
||||||
<form class="pure-form pure-form-aligned" method="POST">
|
<form class="pure-form pure-form-aligned" method="POST">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>{{ template "form/title" . }}</legend>
|
|
||||||
|
|
||||||
{{ block "form/message" . }}
|
{{ block "form/message" . }}
|
||||||
{{ $E := .Error }}
|
{{ $E := .Error }}
|
||||||
{{ if not (eq $E "") }}
|
{{ if not (eq $E "") }}
|
||||||
|
|
@ -26,5 +20,3 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ end }}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue