Files
nidus/internal/web/templates/login_templ.go
T
arnefandCopilot ab3c7f44d5 Add web UI: login, dashboard, and share management (templ + Tailwind + htmx)
New internal/web package mounted at /ui/, separate from DAV Basic Auth:

- Cookie-based sessions (opaque random tokens in a new web_sessions
  SQLite table, internal/db/sessions.go), checked against the same
  cfg.Users/bcrypt credentials as DAV Basic Auth.
- Dashboard listing the logged-in user's own calendars/address books,
  who they're shared with, and what's shared with them.
- Share/unshare directly from the dashboard, updated in place via htmx
  partial swaps (POST to create/update, DELETE to revoke). Always
  verifies the resource actually belongs to the logged-in user before
  granting a share.
- Templates written in templ (internal/web/templates/*.templ, generated
  *_templ.go committed), styled with Tailwind CSS v4 (web/input.css,
  compiled to web/static/app.css), with htmx vendored as a static file
  for the dynamic bits. Both are embedded into the binary at build time
  (web/staticassets.go) so the compiled server has no Node.js/web/
  runtime dependency.
- Wired into cmd/server/main.go at /ui/ alongside the existing /cal/,
  /card/, /files/ routes; welcome page links to it.
- Tests: internal/web/server_test.go covers login success/failure, the
  login-required redirect, dashboard rendering, share/unshare including
  the htmx-v2-sends-DELETE-params-as-query-string quirk, and rejecting
  shares of resources the user doesn't own.
- Docs: README (new 'Web UI' section, updated sharing section, project
  layout, dependencies) and copilot-instructions updated accordingly.
  Makefile: new templ-generate/web-deps/web-css targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-19 07:12:56 +02:00

64 lines
3.5 KiB
Go

// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.1020
package templates
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
func Login(errorMsg string) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<!doctype html><html lang=\"en\" class=\"h-full bg-gray-50\"><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>Login · nidus</title><link rel=\"stylesheet\" href=\"/ui/static/app.css\"></head><body class=\"h-full flex items-center justify-center\"><div class=\"w-full max-w-sm bg-white p-8 rounded-lg shadow-sm border border-gray-200\"><h1 class=\"text-xl font-semibold mb-6 text-center\">nidus</h1>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if errorMsg != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<p class=\"mb-4 text-sm text-red-600 bg-red-50 border border-red-200 rounded px-3 py-2\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(errorMsg)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/login.templ`, Line: 16, Col: 102}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</p>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<form method=\"POST\" action=\"/ui/login\" class=\"space-y-4\"><div><label for=\"username\" class=\"block text-sm font-medium text-gray-700\">Username</label> <input id=\"username\" name=\"username\" type=\"text\" required autofocus class=\"mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"></div><div><label for=\"password\" class=\"block text-sm font-medium text-gray-700\">Password</label> <input id=\"password\" name=\"password\" type=\"password\" required class=\"mt-1 block w-full rounded-md border-gray-300 border px-3 py-2 shadow-sm focus:border-indigo-500 focus:ring-indigo-500\"></div><button type=\"submit\" class=\"w-full bg-indigo-600 text-white rounded-md py-2 font-medium hover:bg-indigo-700\">Sign in</button></form></div></body></html>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate