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:
Tom Wiesing 2023-01-20 14:42:37 +01:00
parent 6ede99d7c6
commit d235ee4e5c
No known key found for this signature in database
59 changed files with 869 additions and 777 deletions

View file

@ -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/logger"
"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/rs/zerolog"
@ -32,7 +32,7 @@ type Admin struct {
Policy *policy.Policy
Templating *templates.Templating
Templating *templating.Templating
Purger *purger.Purger
}

View file

@ -10,75 +10,71 @@ import (
"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/server/assets"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templates"
"github.com/FAU-CDI/wisski-distillery/internal/models"
"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/julienschmidt/httprouter"
)
//go:embed "html/components.html"
var componentsHTML []byte
var componentsTemplate = templates.Parse[componentContext]("components.html", componentsHTML, assets.AssetsAdmin)
//go:embed "html/anal.html"
var analHTML []byte
var analTemplate = templating.Parse[analContext](
"anal.html", analHTML, nil,
type componentContext struct {
templates.BaseContext
templating.Assets(assets.AssetsAdmin),
)
type analContext struct {
templating.RuntimeFlags
Analytics lazy.PoolAnalytics
}
func (admin *Admin) components(ctx context.Context) http.Handler {
tpl := componentsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Components", Path: "/admin/components/"},
},
})
tpl := analTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
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) {
cp.Analytics = *admin.Analytics
return tpl.HTMLHandler(func(r *http.Request) (ac analContext, err error) {
ac.Analytics = *admin.Analytics
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 {
tpl := ingredientsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Instance", Path: "* to be updated *"},
{Title: "Ingredients", Path: "* to be updated *"},
},
})
tpl := analTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
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")
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!
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
if err == instances.ErrWissKINotFound {
return ic, httpx.ErrNotFound
return ac, nil, httpx.ErrNotFound
}
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
ic.Analytics = instance.Info().Analytics
ac.Analytics = *instance.Info().Analytics
return
})

View file

@ -10,7 +10,7 @@ import (
"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/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/wisski"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
@ -22,10 +22,14 @@ import (
//go:embed "html/grants.html"
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 {
templates.BaseContext
templating.RuntimeFlags
Error string
@ -38,40 +42,43 @@ type grantsContext struct {
}
func (admin *Admin) grants(ctx context.Context) http.Handler {
tpl := grantsTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Instance", Path: "*to be updated*"},
{Title: "Grants", Path: "*to be updated*"},
},
})
tpl := grantsTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
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 {
return admin.getGrants(r, gaps)
return admin.getGrants(r)
} 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")
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 {
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
if err := r.ParseForm(); err != nil {
return gc, err
return gc, nil, err
}
// read out the form values
@ -84,15 +91,16 @@ func (admin *Admin) postGrants(r *http.Request, gaps *templates.BaseContextGaps)
)
// set the common fields
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 delete {
// delete the user grant
err := admin.Dependencies.Policy.Remove(r.Context(), distilleryUser, slug)
if err != nil {
return gc, err
return gc, nil, err
}
} else {
// update the grant
@ -110,26 +118,29 @@ func (admin *Admin) postGrants(r *http.Request, gaps *templates.BaseContextGaps)
// fetch the grants for the instance
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) {
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/")}
func (gc *grantsContext) use(r *http.Request, slug string, admin *Admin) (funcs []templating.FlagFunc, err error) {
// find the instance itself
gc.instance, err = admin.Dependencies.Instances.WissKI(r.Context(), slug)
if err == instances.ErrWissKINotFound {
return httpx.ErrNotFound
return nil, httpx.ErrNotFound
}
if err != nil {
return err
return nil, err
}
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) {

View file

@ -0,0 +1,139 @@
<div class="pure-u-1-1">
<h2 id="structs">Structs</h2>
</div>
{{ range $name, $comp := .Components }}
<div class="pure-u-1-1" id="{{ $name }}">
<div class="padding">
<div class="overflow">
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th colspan="3">
{{ $name }}
</th>
</tr>
</thead>
<tbody>
{{ range .Groups }}
<tr>
<td>
Implements
</td>
<td colspan="2">
<code><a href="#{{.}}">{{ . }}</a></code><br />
</td>
</tr>
{{ end }}
{{ range $name, $comp := .CFields }}
<tr>
<td>Component Pointer</td>
<td>
<code>{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $comp }}">{{ $comp }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $comp := .DCFields }}
<tr>
<td>Component Pointer</td>
<td>
<code>Dependencies/{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $comp }}">{{ $comp }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $iface := .IFields }}
<tr>
<td>Interface Slice</td>
<td>
<code>{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $iface }}">[]{{ $iface }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $iface := .DIFields }}
<tr>
<td>Interface Slice</td>
<td>
<code>Dependencies/{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $iface }}">[]{{ $iface }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $sig := $comp.Methods }}
<tr>
<td>
Method
</td>
<td>
<code>{{ $name }}</code>
</td>
<td>
<code>{{ $sig }}</code>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
</div>
{{ end }}
<div class="pure-u-1-1">
<h2 id="interfaces">Interfaces</h2>
</div>
{{ range $name, $group := .Groups }}
<div class="pure-u-1-1" id="{{ $name }}">
<div class="padding">
<div class="overflow">
<table class="pure-table pure-table-bordered">
<thead>
<tr>
<th colspan="3">
{{ $name }}
</th>
</tr>
</thead>
<tbody>
{{ range $name, $sig := $group.Methods }}
<tr>
<td>
Method
</td>
<td>
<code>{{ $name }}</code>
</td>
<td>
<code>{{ $sig }}</code>
</td>
</tr>
{{ end }}
{{ range $group.Components }}
<tr>
<td>
Implemented By
</td>
<td colspan="2">
<code><a href="#{{.}}">{{ . }}</a></code>
</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</div>
</div>
{{ end }}

View file

@ -1,6 +0,0 @@
{{ template "_base.html" . }}
{{ define "title" }}Components{{ end }}
{{ define "content" }}
{{ template "_anal.html" .Analytics }}
{{ end }}

View file

@ -1,7 +1,3 @@
{{ template "_base.html" . }}
{{ define "title" }}{{ .Instance.Slug }} - Grants{{ end }}
{{ define "content" }}
{{ $csrf := .CSRF }}
{{ $slug := .Instance.Slug }}
<div class="pure-u-1-1">
@ -123,5 +119,4 @@
{{ range $unused, $drupal := .Drupals }}
<option value="{{ $drupal }}">
{{ end }}
</datalist>
{{ end }}
</datalist>

View file

@ -1,7 +1,3 @@
{{ template "_base.html" . }}
{{ define "title" }}Admin{{ end }}
{{ define "content" }}
<div class="pure-u-1-1">
<h2 id="overview">Distillery Configuration</h2>
</div>
@ -256,5 +252,4 @@
</p>
</div>
</div>
{{end}}
{{ end }}
{{end}}

View file

@ -1,6 +0,0 @@
{{ template "_base.html" . }}
{{ define "title" }}{{ .Instance.Slug }} - Ingredients{{ end }}
{{ define "content" }}
{{ template "_anal.html" .Analytics }}
{{ end }}

View file

@ -1,7 +1,3 @@
{{ template "_base.html" . }}
{{ define "title" }}{{ .Instance.Slug }}{{ end }}
{{ define "content" }}
<div class="pure-u-1-1">
<h2 id="overview">Info &amp; Status</h2>
</div>
@ -484,5 +480,4 @@
<button class="remote-action pure-button pure-button-danger" data-action="purge" data-param="{{ .Instance.Slug }}" data-confirm-param="#purge-confirm-slug" data-buffer="1000" data-force-reload="/admin/">Purge Instance</button>
</fieldset>
</form>
</div>
{{ end }}
</div>

View file

@ -1,4 +1,2 @@
{{ template "_form.html" . }}
{{ define "form/title" }}Create User{{ end }}
{{ template "form.html" . }}
{{ define "form/button" }}Create{{ end }}

View file

@ -1,8 +1,3 @@
{{ template "_base.html" . }}
{{ define "title" }}Users{{ end }}
{{ define "content" }}
<div class="pure-u-1">
<div class="padding">
<div class="overflow">
@ -105,5 +100,3 @@
</div>
</div>
</div>
{{ end }}

View file

@ -9,7 +9,7 @@ import (
"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/templates"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
"github.com/FAU-CDI/wisski-distillery/internal/status"
"golang.org/x/sync/errgroup"
)
@ -80,27 +80,33 @@ func (admin *Admin) Fetch(flags component.FetcherFlags, target *status.Distiller
//go:embed "html/index.html"
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 {
templates.BaseContext
templating.RuntimeFlags
status.Distillery
Instances []status.WissKI
}
func (admin *Admin) index(ctx context.Context) http.Handler {
tpl := indexTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
},
Actions: []component.MenuItem{
{Title: "Users", Path: "/admin/users/"},
{Title: "Components", Path: "/admin/components/", Priority: component.SmallButton},
},
})
tpl := indexTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
),
templating.Actions(
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)
return
})

View file

@ -9,7 +9,7 @@ import (
"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/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/status"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
@ -18,49 +18,57 @@ import (
//go:embed "html/instance.html"
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 {
templates.BaseContext
templating.RuntimeFlags
Instance models.Instance
Info status.WissKI
}
func (admin *Admin) instance(ctx context.Context) http.Handler {
tpl := instanceTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Instance", Path: "*to be replaced*"},
},
Actions: []component.MenuItem{
{Title: "Grants", Path: "*to be replaced*"},
{Title: "Ingredients", Path: "*to be replaced*", Priority: component.SmallButton},
},
})
tpl := instanceTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
component.DummyMenuItem,
),
templating.Actions(
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")
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!
instance, err := admin.Dependencies.Instances.WissKI(r.Context(), slug)
if err == instances.ErrWissKINotFound {
return ic, httpx.ErrNotFound
return ic, nil, httpx.ErrNotFound
}
if err != nil {
return ic, err
return ic, nil, err
}
ic.Instance = instance.Instance
// get some more info about the wisski
ic.Info, err = instance.Info().Information(r.Context(), false)
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

View file

@ -11,7 +11,7 @@ import (
"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/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/field"
"github.com/rs/zerolog"
@ -19,25 +19,30 @@ import (
//go:embed "html/users.html"
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 {
templates.BaseContext
templating.RuntimeFlags
Error string
Users []*auth.AuthUser
}
func (admin *Admin) users(ctx context.Context) http.Handler {
tpl := usersTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Users", Path: "/admin/users/"},
},
Actions: []component.MenuItem{
{Title: "Create New", Path: "/admin/users/create/"},
},
})
tpl := usersTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
component.MenuItem{Title: "Users", Path: "/admin/users/"},
),
templating.Actions(
component.MenuItem{Title: "Create New", Path: "/admin/users/create/"},
),
)
return tpl.HTMLHandler(func(r *http.Request) (uc usersContext, err 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"
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 (
errCreateInvalidUsername = errors.New("invalid username")
@ -62,13 +72,14 @@ type createUserResult struct {
}
func (admin *Admin) createUser(ctx context.Context) http.Handler {
tpl := userCreateTemplate.Prepare(admin.Dependencies.Templating, templates.BaseContextGaps{
Crumbs: []component.MenuItem{
{Title: "Admin", Path: "/admin/"},
{Title: "Users", Path: "/admin/users"},
{Title: "Create", Path: "/admin/users/create"},
},
})
tpl := userCreateTemplate.Prepare(
admin.Dependencies.Templating,
templating.Crumbs(
component.MenuItem{Title: "Admin", Path: "/admin/"},
component.MenuItem{Title: "Users", Path: "/admin/users"},
component.MenuItem{Title: "Create", Path: "/admin/users/create"},
),
)
return &httpx.Form[createUserResult]{
Fields: []field.Field{
@ -79,7 +90,7 @@ func (admin *Admin) createUser(ctx context.Context) http.Handler {
FieldTemplate: field.PureCSSFieldTemplate,
RenderTemplate: tpl.Template(),
RenderTemplateContext: templates.FormTemplateContext(tpl),
RenderTemplateContext: templating.FormTemplateContext(tpl),
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