cerrado @ 1e45ae2ea3497958b2ea6a20137955cfc3bbc964

feat: Add UI/Handler login process

It adds the whole workflow to store and handle login on both UI and
handler level. With that the login information should be available at
any point given the context.
   1diff --git a/main.go b/main.go
   2index ab4aee90d096f54cc9235ea4b5e48c27a206774b..797ebea41f73cc63fed21d9fdd12a7cd2bbe4f78 100644
   3--- a/main.go
   4+++ b/main.go
   5@@ -74,8 +74,7 @@ 	if err != nil {
   6 		return err
   7 	}
   8 
   9-	// checking chroma configurationo
  10-
  11+	// checking chroma configuration
  12 	if _, ok := styles.Registry[configRepo.GetSyntaxHighlight()]; !ok {
  13 		slog.Warn(
  14 			"Invalid Syntax highlight selected",
  15@@ -87,8 +86,13 @@ 	}
  16 
  17 	// services
  18 	gitService := service.NewGitService(configRepo)
  19+	authService := service.NewAuthService(configRepo)
  20 
  21-	handler, err := handler.MountHandler(gitService, configRepo)
  22+	handler, err := handler.MountHandler(
  23+		gitService,
  24+		authService,
  25+		configRepo,
  26+	)
  27 	if err != nil {
  28 		return err
  29 	}
  30diff --git a/pkg/config/config.go b/pkg/config/config.go
  31index 812a06ef7ead3d270cd1b2d7d5d31a3033aabc5e..da6e0e7044745edf7ef9a5b3e720f97b2ce62df0 100644
  32--- a/pkg/config/config.go
  33+++ b/pkg/config/config.go
  34@@ -55,8 +55,8 @@ 	// information.
  35 	ConfigurationRepository struct {
  36 		rootReadme      string
  37 		listenAddr      string
  38-		passphrase      string
  39-		aesKey          string
  40+		passphrase      []byte
  41+		aesKey          []byte
  42 		syntaxHighlight string
  43 		repositories    []*GitRepositoryConfiguration
  44 	}
  45@@ -74,9 +74,9 @@ 		return nil, err
  46 	}
  47 
  48 	repo := &ConfigurationRepository{
  49-		aesKey:          config.AESKey,
  50+		aesKey:          []byte(config.AESKey),
  51 		listenAddr:      config.ListenAddr,
  52-		passphrase:      config.Passphrase,
  53+		passphrase:      []byte(config.Passphrase),
  54 		repositories:    config.Repositories,
  55 		rootReadme:      config.RootReadme,
  56 		syntaxHighlight: config.SyntaxHighlight,
  57@@ -103,6 +103,14 @@ }
  58 
  59 func (c *ConfigurationRepository) GetListenAddr() string {
  60 	return c.listenAddr
  61+}
  62+
  63+func (c *ConfigurationRepository) GetPassphrase() []byte {
  64+	return c.passphrase
  65+}
  66+
  67+func (c *ConfigurationRepository) GetBase64AesKey() []byte {
  68+	return c.aesKey
  69 }
  70 
  71 // GetByName returns configuration of repository for a given name.
  72diff --git a/pkg/ext/auth.go b/pkg/ext/auth.go
  73new file mode 100644
  74index 0000000000000000000000000000000000000000..bb6c0a2c07f23394ead3809aa54390326fd0fcd0
  75--- /dev/null
  76+++ b/pkg/ext/auth.go
  77@@ -0,0 +1,45 @@
  78+package ext
  79+
  80+import (
  81+	"context"
  82+	"encoding/base64"
  83+	"log/slog"
  84+	"net/http"
  85+)
  86+
  87+type authService interface {
  88+	ValidateToken(token []byte) (bool, error)
  89+}
  90+
  91+func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc {
  92+	return func(next http.HandlerFunc) http.HandlerFunc {
  93+		return func(w http.ResponseWriter, r *http.Request) {
  94+			cookie, err := r.Cookie("auth")
  95+			if err != nil {
  96+				slog.Error("Error loading cookie", "error", err)
  97+				next(w, r)
  98+				return
  99+			}
 100+
 101+			value, err := base64.StdEncoding.DecodeString(cookie.Value)
 102+			if err != nil {
 103+				slog.Error("Error decoding", "error", err)
 104+				next(w, r)
 105+				return
 106+			}
 107+
 108+			valid, err := auth.ValidateToken(value)
 109+			if err != nil {
 110+				slog.Error("Error validating token", "error", err, "cookie", cookie.Value)
 111+				next(w, r)
 112+				return
 113+			}
 114+
 115+			ctx := r.Context()
 116+			ctx = context.WithValue(ctx, "logged", true)
 117+
 118+			slog.Info("Validated token", "valid?", valid)
 119+			next(w, r.WithContext(ctx))
 120+		}
 121+	}
 122+}
 123diff --git a/pkg/ext/router.go b/pkg/ext/router.go
 124index 96da1c9399992e589bbd4fefdb0bfc78b5e59b80..956254d06334a443ea3e66b37bded75847120230 100644
 125--- a/pkg/ext/router.go
 126+++ b/pkg/ext/router.go
 127@@ -23,6 +23,7 @@ 	return &Router{
 128 		router: http.NewServeMux(),
 129 	}
 130 }
 131+
 132 func (r *Router) Handler() http.Handler {
 133 	return r.router
 134 }
 135@@ -35,9 +36,9 @@ func wrapError(next ErrorRequestHandler) http.HandlerFunc {
 136 	return func(w http.ResponseWriter, r *http.Request) {
 137 		if err := next(w, r); err != nil {
 138 			if errors.Is(err, service.ErrRepositoryNotFound) {
 139-				NotFound(w)
 140+				NotFound(w, r)
 141 			} else {
 142-				InternalServerError(w, err)
 143+				InternalServerError(r, w, err)
 144 			}
 145 		}
 146 	}
 147@@ -57,16 +58,21 @@ func (r *Router) HandleFunc(path string, handler ErrorRequestHandler) {
 148 	r.router.HandleFunc(path, r.run(handler))
 149 }
 150 
 151-func NotFound(w http.ResponseWriter) {
 152+func NotFound(w http.ResponseWriter, r *http.Request) {
 153 	w.WriteHeader(http.StatusNotFound)
 154 	templates.WritePageTemplate(w, &templates.ErrorPage{
 155 		Message: "Not Found",
 156-	})
 157+	}, r.Context())
 158 }
 159 
 160-func InternalServerError(w http.ResponseWriter, err error) {
 161+func Redirect(w http.ResponseWriter, location string) {
 162+	w.Header().Add("location", location)
 163+	w.WriteHeader(http.StatusTemporaryRedirect)
 164+}
 165+
 166+func InternalServerError(r *http.Request, w http.ResponseWriter, err error) {
 167 	w.WriteHeader(http.StatusInternalServerError)
 168 	templates.WritePageTemplate(w, &templates.ErrorPage{
 169 		Message: fmt.Sprintf("Internal Server Error:\n%s", err.Error()),
 170-	})
 171+	}, r.Context())
 172 }
 173diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go
 174index ac3d31417836e98121bd03807b2993fa2dbf27cb..ee084cd38dc3bf6124cd8e7462005446cfb0222c 100644
 175--- a/pkg/handler/about/handler.go
 176+++ b/pkg/handler/about/handler.go
 177@@ -26,7 +26,7 @@ func NewAboutHandler(configRepo configurationRepository) *AboutHandler {
 178 	return &AboutHandler{configRepo.GetRootReadme()}
 179 }
 180 
 181-func (g *AboutHandler) About(w http.ResponseWriter, _ *http.Request) error {
 182+func (g *AboutHandler) About(w http.ResponseWriter, r *http.Request) error {
 183 	f, err := os.Open(g.readmePath)
 184 	if err != nil {
 185 		return err
 186@@ -50,6 +50,6 @@
 187 	gitList := &templates.AboutPage{
 188 		Body: bs,
 189 	}
 190-	templates.WritePageTemplate(w, gitList)
 191+	templates.WritePageTemplate(w, gitList, r.Context())
 192 	return nil
 193 }
 194diff --git a/pkg/handler/auth/login.go b/pkg/handler/auth/login.go
 195index 7e77a67ba295d77bb0b742eb3ccefea98d9fd411..7014548c0965def2eba242a03ae269b8c27f7a00 100644
 196--- a/pkg/handler/auth/login.go
 197+++ b/pkg/handler/auth/login.go
 198@@ -1,20 +1,87 @@
 199 package auth
 200 
 201 import (
 202+	"encoding/base64"
 203 	"net/http"
 204+	"time"
 205 
 206 	"git.gabrielgio.me/cerrado/pkg/ext"
 207 	"git.gabrielgio.me/cerrado/templates"
 208 )
 209 
 210 type (
 211-	LoginHandler struct{}
 212+	LoginHandler struct {
 213+		auth authService
 214+	}
 215+
 216+	authService interface {
 217+		CheckAuth(username, password string) bool
 218+		IssueToken() ([]byte, error)
 219+	}
 220 )
 221 
 222+func NewLoginHandler(auth authService) *LoginHandler {
 223+	return &LoginHandler{
 224+		auth: auth,
 225+	}
 226+}
 227+
 228+func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error {
 229+	cookie := &http.Cookie{
 230+		Name:    "auth",
 231+		Value:   "",
 232+		Path:    "/",
 233+		Expires: time.Unix(0, 0),
 234+	}
 235+
 236+	referer := r.Header.Get("Referer")
 237+	if referer == "" {
 238+		referer = "/"
 239+	}
 240+
 241+	http.SetCookie(w, cookie)
 242+	ext.Redirect(w, referer)
 243+	return nil
 244+}
 245+
 246 func (g *LoginHandler) Login(w http.ResponseWriter, r *http.Request) error {
 247-	ext.SetHTML(w)
 248+	if r.Method == "GET" {
 249+		ext.SetHTML(w)
 250+
 251+		login := &templates.LoginPage{}
 252+		templates.WritePageTemplate(w, login, r.Context())
 253+	} else if r.Method == "POST" {
 254+
 255+		username := r.FormValue("username")
 256+		password := r.FormValue("password")
 257+
 258+		if !g.auth.CheckAuth(username, password) {
 259+			login := &templates.LoginPage{
 260+				ErrorMessage: "Invalid login",
 261+			}
 262+			templates.WritePageTemplate(w, login, r.Context())
 263+		} else {
 264+
 265+			bytes, err := g.auth.IssueToken()
 266+			if err != nil {
 267+				return err
 268+			}
 269+
 270+			cookie := &http.Cookie{
 271+				Name:     "auth",
 272+				Value:    base64.StdEncoding.EncodeToString(bytes),
 273+				Path:     "/",
 274+				MaxAge:   3600,
 275+				HttpOnly: true,
 276+				Secure:   true,
 277+				SameSite: http.SameSiteStrictMode,
 278+			}
 279+
 280+			http.SetCookie(w, cookie)
 281+			ext.Redirect(w, "/")
 282+		}
 283 
 284-	login := &templates.LoginPage{}
 285-	templates.WritePageTemplate(w, login)
 286+	}
 287+
 288 	return nil
 289 }
 290diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
 291index 5739c8e9dbca0c7b015224ed3af26f53118eb72c..42761595323c97d0ea49dc3fe69d618446284bcc 100644
 292--- a/pkg/handler/git/handler.go
 293+++ b/pkg/handler/git/handler.go
 294@@ -43,7 +43,7 @@ 		config:     confRepo,
 295 	}
 296 }
 297 
 298-func (g *GitHandler) List(w http.ResponseWriter, _ *http.Request) error {
 299+func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error {
 300 	repos, err := g.gitService.ListRepositories()
 301 	if err != nil {
 302 		return err
 303@@ -73,7 +73,7 @@ 	gitList := &templates.GitListPage{
 304 		Respositories: repos,
 305 		About:         bs,
 306 	}
 307-	templates.WritePageTemplate(w, gitList)
 308+	templates.WritePageTemplate(w, gitList, r.Context())
 309 	return nil
 310 }
 311 
 312@@ -85,7 +85,7 @@ 	ref := strings.TrimSuffix(file, ".tar.gz")
 313 
 314 	// TODO: remove it once we can support more than gzip
 315 	if !strings.HasSuffix(file, ".tar.gz") {
 316-		ext.NotFound(w)
 317+		ext.NotFound(w, r)
 318 		return nil
 319 	}
 320 
 321@@ -135,7 +135,7 @@ 			Branches: branches,
 322 			Commits:  commits,
 323 		},
 324 	}
 325-	templates.WritePageTemplate(w, gitList)
 326+	templates.WritePageTemplate(w, gitList, r.Context())
 327 	return nil
 328 }
 329 
 330@@ -155,7 +155,7 @@ 			Ref:  ref.Name().Short(),
 331 			GitItemBase: &templates.GitItemAboutPage{
 332 				About: []byte("About file not configured properly"),
 333 			},
 334-		})
 335+		}, r.Context())
 336 		return nil
 337 	}
 338 	if err != nil {
 339@@ -179,7 +179,7 @@ 		GitItemBase: &templates.GitItemAboutPage{
 340 			About: bs,
 341 		},
 342 	}
 343-	templates.WritePageTemplate(w, gitList)
 344+	templates.WritePageTemplate(w, gitList, r.Context())
 345 	return nil
 346 }
 347 
 348@@ -210,7 +210,7 @@ 			Tags:     tags,
 349 			Branches: branches,
 350 		},
 351 	}
 352-	templates.WritePageTemplate(w, gitList)
 353+	templates.WritePageTemplate(w, gitList, r.Context())
 354 	return nil
 355 }
 356 
 357@@ -239,7 +239,7 @@ 			Path: paths,
 358 			Tree: tree,
 359 		},
 360 	}
 361-	templates.WritePageTemplate(w, gitList)
 362+	templates.WritePageTemplate(w, gitList, r.Context())
 363 	return nil
 364 }
 365 
 366@@ -270,7 +270,7 @@ 				Path:    paths,
 367 				Content: []byte("Binary file"),
 368 			},
 369 		}
 370-		templates.WritePageTemplate(w, gitList)
 371+		templates.WritePageTemplate(w, gitList, r.Context())
 372 		return nil
 373 	}
 374 
 375@@ -307,7 +307,7 @@ 			Path:    paths,
 376 			Content: code.Bytes(),
 377 		},
 378 	}
 379-	templates.WritePageTemplate(w, gitList)
 380+	templates.WritePageTemplate(w, gitList, r.Context())
 381 	return nil
 382 }
 383 
 384@@ -328,7 +328,7 @@ 		GitItemBase: &templates.GitItemLogPage{
 385 			Commits: commits,
 386 		},
 387 	}
 388-	templates.WritePageTemplate(w, gitList)
 389+	templates.WritePageTemplate(w, gitList, r.Context())
 390 	return nil
 391 }
 392 
 393@@ -355,7 +355,7 @@ 			Commit: commit,
 394 			Diff:   diff,
 395 		},
 396 	}
 397-	templates.WritePageTemplate(w, gitList)
 398+	templates.WritePageTemplate(w, gitList, r.Context())
 399 	return nil
 400 }
 401 
 402diff --git a/pkg/handler/router.go b/pkg/handler/router.go
 403index 32bd78adff98f82bca843aba96d2ddb85d7c79f7..ee4081b504b8291e24926062dfb9bc1765119874 100644
 404--- a/pkg/handler/router.go
 405+++ b/pkg/handler/router.go
 406@@ -17,12 +17,13 @@ // This functons wraps the whole handler package and wraps it into one part so
 407 // its sub package don't leak in other places.
 408 func MountHandler(
 409 	gitService *service.GitService,
 410+	authService *service.AuthService,
 411 	configRepo *serverconfig.ConfigurationRepository,
 412 ) (http.Handler, error) {
 413 	var (
 414 		gitHandler   = git.NewGitHandler(gitService, configRepo)
 415 		aboutHandler = about.NewAboutHandler(configRepo)
 416-		loginHandler = &auth.LoginHandler{}
 417+		loginHandler = auth.NewLoginHandler(authService)
 418 	)
 419 
 420 	staticHandler, err := static.ServeStaticHandler()
 421@@ -32,10 +33,12 @@ 	}
 422 
 423 	mux := ext.NewRouter()
 424 	mux.AddMiddleware(ext.Compress)
 425+	mux.AddMiddleware(ext.Authenticate(authService))
 426 	mux.AddMiddleware(ext.Log)
 427 
 428 	mux.HandleFunc("/static/{file}", staticHandler)
 429 	mux.HandleFunc("/login/{$}", loginHandler.Login)
 430+	mux.HandleFunc("/logout/{$}", loginHandler.Logout)
 431 	mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
 432 	mux.HandleFunc("/{name}/", gitHandler.Summary)
 433 	mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
 434diff --git a/pkg/service/auth.go b/pkg/service/auth.go
 435index 1fbf4b61724c079a9557bc675846ba94d1ad370e..0dbd960ff2a49368f531da1b5ceb58719c8537fe 100644
 436--- a/pkg/service/auth.go
 437+++ b/pkg/service/auth.go
 438@@ -23,7 +23,13 @@ 		GetBase64AesKey() []byte
 439 	}
 440 )
 441 
 442-var tokenSeed = []byte("cerrado")
 443+var tokenSeed = []byte("this is a token for cerrado")
 444+
 445+func NewAuthService(repostiory authRepository) *AuthService {
 446+	return &AuthService{
 447+		authRepository: repostiory,
 448+	}
 449+}
 450 
 451 func (a *AuthService) CheckAuth(username, password string) bool {
 452 	passphrase := a.authRepository.GetPassphrase()
 453diff --git a/pkg/service/git.go b/pkg/service/git.go
 454index f03ba426f3933a7bf8f1a9f1ff15e6b88613954a..6c3912fae3c57a3500849ef2e15d2616b838f144 100644
 455--- a/pkg/service/git.go
 456+++ b/pkg/service/git.go
 457@@ -30,9 +30,7 @@ 		GetByName(name string) *config.GitRepositoryConfiguration
 458 	}
 459 )
 460 
 461-var (
 462-	ErrRepositoryNotFound = errors.New("Repository not found")
 463-)
 464+var ErrRepositoryNotFound = errors.New("Repository not found")
 465 
 466 // TODO: make it configurable
 467 const timeFormat = "2006.01.02 15:04:05"
 468diff --git a/templates/about.qtpl b/templates/about.qtpl
 469index faee50e18821c54e739efd5611c0b548cf94cc15..cfbf0d9001e0dea884eb1096834169bc2d620496 100644
 470--- a/templates/about.qtpl
 471+++ b/templates/about.qtpl
 472@@ -1,16 +1,19 @@
 473+{% import "context" %}
 474+
 475 {% code
 476 type AboutPage struct {
 477+    LoggedIn bool
 478     Body []byte
 479 }
 480 %}
 481 
 482-{% func (p *AboutPage) Title() %}Hello{% endfunc %}
 483+{% func (p *AboutPage) Title(ctx context.Context) %}Hello{% endfunc %}
 484 
 485-{% func (p *AboutPage) Navbar() %}{%= Navbar(About) %}{% endfunc %}
 486+{% func (p *AboutPage) Navbar(ctx context.Context) %}{%= Navbar(ctx, About) %}{% endfunc %}
 487 
 488-{% func (p *AboutPage) Content() %}
 489+{% func (p *AboutPage) Content(ctx context.Context) %}
 490 {%z= p.Body %}
 491 {% endfunc %}
 492 
 493-{% func (p *AboutPage) Script() %}
 494+{% func (p *AboutPage) Script(ctx context.Context) %}
 495 {% endfunc %}
 496diff --git a/templates/about.qtpl.go b/templates/about.qtpl.go
 497index 2c07ceb1818619df56672b4bea08b8b1af0bcb62..a640f7eddc849adab11651d44c5644ad367240d4 100644
 498--- a/templates/about.qtpl.go
 499+++ b/templates/about.qtpl.go
 500@@ -5,130 +5,100 @@ //line templates/about.qtpl:1
 501 package templates
 502 
 503 //line templates/about.qtpl:1
 504+import "context"
 505+
 506+//line templates/about.qtpl:3
 507 import (
 508 	qtio422016 "io"
 509 
 510 	qt422016 "github.com/valyala/quicktemplate"
 511 )
 512 
 513-//line templates/about.qtpl:1
 514+//line templates/about.qtpl:3
 515 var (
 516 	_ = qtio422016.Copy
 517 	_ = qt422016.AcquireByteBuffer
 518 )
 519 
 520-//line templates/about.qtpl:2
 521+//line templates/about.qtpl:4
 522 type AboutPage struct {
 523-	Body []byte
 524+	LoggedIn bool
 525+	Body     []byte
 526 }
 527 
 528-//line templates/about.qtpl:7
 529-func (p *AboutPage) StreamTitle(qw422016 *qt422016.Writer) {
 530-//line templates/about.qtpl:7
 531+//line templates/about.qtpl:10
 532+func (p *AboutPage) StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) {
 533+//line templates/about.qtpl:10
 534 	qw422016.N().S(`Hello`)
 535-//line templates/about.qtpl:7
 536-}
 537-
 538-//line templates/about.qtpl:7
 539-func (p *AboutPage) WriteTitle(qq422016 qtio422016.Writer) {
 540-//line templates/about.qtpl:7
 541-	qw422016 := qt422016.AcquireWriter(qq422016)
 542-//line templates/about.qtpl:7
 543-	p.StreamTitle(qw422016)
 544-//line templates/about.qtpl:7
 545-	qt422016.ReleaseWriter(qw422016)
 546-//line templates/about.qtpl:7
 547-}
 548-
 549-//line templates/about.qtpl:7
 550-func (p *AboutPage) Title() string {
 551-//line templates/about.qtpl:7
 552-	qb422016 := qt422016.AcquireByteBuffer()
 553-//line templates/about.qtpl:7
 554-	p.WriteTitle(qb422016)
 555-//line templates/about.qtpl:7
 556-	qs422016 := string(qb422016.B)
 557-//line templates/about.qtpl:7
 558-	qt422016.ReleaseByteBuffer(qb422016)
 559-//line templates/about.qtpl:7
 560-	return qs422016
 561-//line templates/about.qtpl:7
 562-}
 563-
 564-//line templates/about.qtpl:9
 565-func (p *AboutPage) StreamNavbar(qw422016 *qt422016.Writer) {
 566-//line templates/about.qtpl:9
 567-	StreamNavbar(qw422016, About)
 568-//line templates/about.qtpl:9
 569+//line templates/about.qtpl:10
 570 }
 571 
 572-//line templates/about.qtpl:9
 573-func (p *AboutPage) WriteNavbar(qq422016 qtio422016.Writer) {
 574-//line templates/about.qtpl:9
 575+//line templates/about.qtpl:10
 576+func (p *AboutPage) WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) {
 577+//line templates/about.qtpl:10
 578 	qw422016 := qt422016.AcquireWriter(qq422016)
 579-//line templates/about.qtpl:9
 580-	p.StreamNavbar(qw422016)
 581-//line templates/about.qtpl:9
 582+//line templates/about.qtpl:10
 583+	p.StreamTitle(qw422016, ctx)
 584+//line templates/about.qtpl:10
 585 	qt422016.ReleaseWriter(qw422016)
 586-//line templates/about.qtpl:9
 587+//line templates/about.qtpl:10
 588 }
 589 
 590-//line templates/about.qtpl:9
 591-func (p *AboutPage) Navbar() string {
 592-//line templates/about.qtpl:9
 593+//line templates/about.qtpl:10
 594+func (p *AboutPage) Title(ctx context.Context) string {
 595+//line templates/about.qtpl:10
 596 	qb422016 := qt422016.AcquireByteBuffer()
 597-//line templates/about.qtpl:9
 598-	p.WriteNavbar(qb422016)
 599-//line templates/about.qtpl:9
 600+//line templates/about.qtpl:10
 601+	p.WriteTitle(qb422016, ctx)
 602+//line templates/about.qtpl:10
 603 	qs422016 := string(qb422016.B)
 604-//line templates/about.qtpl:9
 605+//line templates/about.qtpl:10
 606 	qt422016.ReleaseByteBuffer(qb422016)
 607-//line templates/about.qtpl:9
 608+//line templates/about.qtpl:10
 609 	return qs422016
 610-//line templates/about.qtpl:9
 611+//line templates/about.qtpl:10
 612 }
 613 
 614-//line templates/about.qtpl:11
 615-func (p *AboutPage) StreamContent(qw422016 *qt422016.Writer) {
 616-//line templates/about.qtpl:11
 617-	qw422016.N().S(`
 618-`)
 619 //line templates/about.qtpl:12
 620-	qw422016.N().Z(p.Body)
 621+func (p *AboutPage) StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) {
 622 //line templates/about.qtpl:12
 623-	qw422016.N().S(`
 624-`)
 625-//line templates/about.qtpl:13
 626+	StreamNavbar(qw422016, ctx, About)
 627+//line templates/about.qtpl:12
 628 }
 629 
 630-//line templates/about.qtpl:13
 631-func (p *AboutPage) WriteContent(qq422016 qtio422016.Writer) {
 632-//line templates/about.qtpl:13
 633+//line templates/about.qtpl:12
 634+func (p *AboutPage) WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) {
 635+//line templates/about.qtpl:12
 636 	qw422016 := qt422016.AcquireWriter(qq422016)
 637-//line templates/about.qtpl:13
 638-	p.StreamContent(qw422016)
 639-//line templates/about.qtpl:13
 640+//line templates/about.qtpl:12
 641+	p.StreamNavbar(qw422016, ctx)
 642+//line templates/about.qtpl:12
 643 	qt422016.ReleaseWriter(qw422016)
 644-//line templates/about.qtpl:13
 645+//line templates/about.qtpl:12
 646 }
 647 
 648-//line templates/about.qtpl:13
 649-func (p *AboutPage) Content() string {
 650-//line templates/about.qtpl:13
 651+//line templates/about.qtpl:12
 652+func (p *AboutPage) Navbar(ctx context.Context) string {
 653+//line templates/about.qtpl:12
 654 	qb422016 := qt422016.AcquireByteBuffer()
 655-//line templates/about.qtpl:13
 656-	p.WriteContent(qb422016)
 657-//line templates/about.qtpl:13
 658+//line templates/about.qtpl:12
 659+	p.WriteNavbar(qb422016, ctx)
 660+//line templates/about.qtpl:12
 661 	qs422016 := string(qb422016.B)
 662-//line templates/about.qtpl:13
 663+//line templates/about.qtpl:12
 664 	qt422016.ReleaseByteBuffer(qb422016)
 665-//line templates/about.qtpl:13
 666+//line templates/about.qtpl:12
 667 	return qs422016
 668-//line templates/about.qtpl:13
 669+//line templates/about.qtpl:12
 670 }
 671 
 672+//line templates/about.qtpl:14
 673+func (p *AboutPage) StreamContent(qw422016 *qt422016.Writer, ctx context.Context) {
 674+//line templates/about.qtpl:14
 675+	qw422016.N().S(`
 676+`)
 677 //line templates/about.qtpl:15
 678-func (p *AboutPage) StreamScript(qw422016 *qt422016.Writer) {
 679+	qw422016.N().Z(p.Body)
 680 //line templates/about.qtpl:15
 681 	qw422016.N().S(`
 682 `)
 683@@ -136,22 +106,22 @@ //line templates/about.qtpl:16
 684 }
 685 
 686 //line templates/about.qtpl:16
 687-func (p *AboutPage) WriteScript(qq422016 qtio422016.Writer) {
 688+func (p *AboutPage) WriteContent(qq422016 qtio422016.Writer, ctx context.Context) {
 689 //line templates/about.qtpl:16
 690 	qw422016 := qt422016.AcquireWriter(qq422016)
 691 //line templates/about.qtpl:16
 692-	p.StreamScript(qw422016)
 693+	p.StreamContent(qw422016, ctx)
 694 //line templates/about.qtpl:16
 695 	qt422016.ReleaseWriter(qw422016)
 696 //line templates/about.qtpl:16
 697 }
 698 
 699 //line templates/about.qtpl:16
 700-func (p *AboutPage) Script() string {
 701+func (p *AboutPage) Content(ctx context.Context) string {
 702 //line templates/about.qtpl:16
 703 	qb422016 := qt422016.AcquireByteBuffer()
 704 //line templates/about.qtpl:16
 705-	p.WriteScript(qb422016)
 706+	p.WriteContent(qb422016, ctx)
 707 //line templates/about.qtpl:16
 708 	qs422016 := string(qb422016.B)
 709 //line templates/about.qtpl:16
 710@@ -160,3 +130,37 @@ //line templates/about.qtpl:16
 711 	return qs422016
 712 //line templates/about.qtpl:16
 713 }
 714+
 715+//line templates/about.qtpl:18
 716+func (p *AboutPage) StreamScript(qw422016 *qt422016.Writer, ctx context.Context) {
 717+//line templates/about.qtpl:18
 718+	qw422016.N().S(`
 719+`)
 720+//line templates/about.qtpl:19
 721+}
 722+
 723+//line templates/about.qtpl:19
 724+func (p *AboutPage) WriteScript(qq422016 qtio422016.Writer, ctx context.Context) {
 725+//line templates/about.qtpl:19
 726+	qw422016 := qt422016.AcquireWriter(qq422016)
 727+//line templates/about.qtpl:19
 728+	p.StreamScript(qw422016, ctx)
 729+//line templates/about.qtpl:19
 730+	qt422016.ReleaseWriter(qw422016)
 731+//line templates/about.qtpl:19
 732+}
 733+
 734+//line templates/about.qtpl:19
 735+func (p *AboutPage) Script(ctx context.Context) string {
 736+//line templates/about.qtpl:19
 737+	qb422016 := qt422016.AcquireByteBuffer()
 738+//line templates/about.qtpl:19
 739+	p.WriteScript(qb422016, ctx)
 740+//line templates/about.qtpl:19
 741+	qs422016 := string(qb422016.B)
 742+//line templates/about.qtpl:19
 743+	qt422016.ReleaseByteBuffer(qb422016)
 744+//line templates/about.qtpl:19
 745+	return qs422016
 746+//line templates/about.qtpl:19
 747+}
 748diff --git a/templates/base.qtpl b/templates/base.qtpl
 749index 566308f9c13c670c3106c8ba2c51230e533e00d8..2a42cb889be45ffeb308c0aae56cd37666f9e2f8 100644
 750--- a/templates/base.qtpl
 751+++ b/templates/base.qtpl
 752@@ -1,5 +1,6 @@
 753 This is a base page template. All the other template pages implement this interface.
 754 
 755+{% import "context" %}
 756 {% import "strconv" %}
 757 {% import "time" %}
 758 
 759@@ -9,10 +10,10 @@ %}
 760 
 761 {% interface
 762 Page {
 763-	Title()
 764-	Content()
 765-    Script()
 766-    Navbar()
 767+	Title(ctx context.Context)
 768+	Content(ctx context.Context)
 769+    Script(ctx context.Context)
 770+    Navbar(ctx context.Context)
 771 }
 772 %}
 773 
 774@@ -24,6 +25,8 @@         }
 775         return ""
 776     }
 777 %}
 778+
 779+
 780 
 781 {% code func TimeFormat(t time.Time) string {
 782         return t.Format("02.01.2006")
 783@@ -35,24 +38,30 @@         return v
 784     }
 785 %}
 786 
 787+{% code func IsLoggedIn(ctx context.Context) bool {
 788+	t, ok := ctx.Value("logged").(bool)
 789+	return ok && t
 790+    }
 791+%}
 792+
 793 Page prints a page implementing Page interface.
 794-{% func PageTemplate(p Page) %}
 795+{% func PageTemplate(p Page, ctx context.Context) %}
 796 <!DOCTYPE html>
 797 <html lang="en">
 798   <head>
 799     <meta charset="utf-8">
 800     <link rel="icon" href="data:,">
 801-    <title>{%= p.Title() %}</title> 
 802+    <title>{%= p.Title(ctx) %}</title> 
 803     <link rel="stylesheet" href="/static/main{%s Slug %}.css">
 804     <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
 805     <meta name="viewport" content="width=device-width, initial-scale=1" />
 806   </head>
 807   <body>
 808-    {%= p.Navbar() %}
 809+    {%= p.Navbar(ctx) %}
 810     <div class="container">
 811-      {%= p.Content() %}
 812+      {%= p.Content(ctx) %}
 813     </div>
 814   </body>
 815-  {%= p.Script() %}
 816+  {%= p.Script(ctx) %}
 817 </html>
 818 {% endfunc %}
 819diff --git a/templates/base.qtpl.go b/templates/base.qtpl.go
 820index f8ff91f23386053a256a62cf482aaaaa4f68d5f7..5bb45323a0d55b6106c12d8370671c6e3e909553 100644
 821--- a/templates/base.qtpl.go
 822+++ b/templates/base.qtpl.go
 823@@ -8,57 +8,60 @@ //line templates/base.qtpl:3
 824 package templates
 825 
 826 //line templates/base.qtpl:3
 827+import "context"
 828+
 829+//line templates/base.qtpl:4
 830 import "strconv"
 831 
 832-//line templates/base.qtpl:4
 833+//line templates/base.qtpl:5
 834 import "time"
 835 
 836-//line templates/base.qtpl:6
 837+//line templates/base.qtpl:7
 838 import (
 839 	qtio422016 "io"
 840 
 841 	qt422016 "github.com/valyala/quicktemplate"
 842 )
 843 
 844-//line templates/base.qtpl:6
 845+//line templates/base.qtpl:7
 846 var (
 847 	_ = qtio422016.Copy
 848 	_ = qt422016.AcquireByteBuffer
 849 )
 850 
 851-//line templates/base.qtpl:7
 852+//line templates/base.qtpl:8
 853 var Slug = ""
 854 
 855-//line templates/base.qtpl:11
 856+//line templates/base.qtpl:12
 857 type Page interface {
 858-//line templates/base.qtpl:11
 859-	Title() string
 860-//line templates/base.qtpl:11
 861-	StreamTitle(qw422016 *qt422016.Writer)
 862-//line templates/base.qtpl:11
 863-	WriteTitle(qq422016 qtio422016.Writer)
 864-//line templates/base.qtpl:11
 865-	Content() string
 866-//line templates/base.qtpl:11
 867-	StreamContent(qw422016 *qt422016.Writer)
 868-//line templates/base.qtpl:11
 869-	WriteContent(qq422016 qtio422016.Writer)
 870-//line templates/base.qtpl:11
 871-	Script() string
 872-//line templates/base.qtpl:11
 873-	StreamScript(qw422016 *qt422016.Writer)
 874-//line templates/base.qtpl:11
 875-	WriteScript(qq422016 qtio422016.Writer)
 876-//line templates/base.qtpl:11
 877-	Navbar() string
 878-//line templates/base.qtpl:11
 879-	StreamNavbar(qw422016 *qt422016.Writer)
 880-//line templates/base.qtpl:11
 881-	WriteNavbar(qq422016 qtio422016.Writer)
 882-//line templates/base.qtpl:11
 883+//line templates/base.qtpl:12
 884+	Title(ctx context.Context) string
 885+//line templates/base.qtpl:12
 886+	StreamTitle(qw422016 *qt422016.Writer, ctx context.Context)
 887+//line templates/base.qtpl:12
 888+	WriteTitle(qq422016 qtio422016.Writer, ctx context.Context)
 889+//line templates/base.qtpl:12
 890+	Content(ctx context.Context) string
 891+//line templates/base.qtpl:12
 892+	StreamContent(qw422016 *qt422016.Writer, ctx context.Context)
 893+//line templates/base.qtpl:12
 894+	WriteContent(qq422016 qtio422016.Writer, ctx context.Context)
 895+//line templates/base.qtpl:12
 896+	Script(ctx context.Context) string
 897+//line templates/base.qtpl:12
 898+	StreamScript(qw422016 *qt422016.Writer, ctx context.Context)
 899+//line templates/base.qtpl:12
 900+	WriteScript(qq422016 qtio422016.Writer, ctx context.Context)
 901+//line templates/base.qtpl:12
 902+	Navbar(ctx context.Context) string
 903+//line templates/base.qtpl:12
 904+	StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context)
 905+//line templates/base.qtpl:12
 906+	WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context)
 907+//line templates/base.qtpl:12
 908 }
 909 
 910-//line templates/base.qtpl:20
 911+//line templates/base.qtpl:21
 912 func FromUInttoString(u *uint) string {
 913 	if u != nil {
 914 		return strconv.FormatUint(uint64(*u), 10)
 915@@ -66,21 +69,27 @@ 	}
 916 	return ""
 917 }
 918 
 919-//line templates/base.qtpl:28
 920+//line templates/base.qtpl:31
 921 func TimeFormat(t time.Time) string {
 922 	return t.Format("02.01.2006")
 923 }
 924 
 925-//line templates/base.qtpl:33
 926+//line templates/base.qtpl:36
 927 func Ignore[T any](v T, _ error) T {
 928 	return v
 929 }
 930 
 931+//line templates/base.qtpl:41
 932+func IsLoggedIn(ctx context.Context) bool {
 933+	t, ok := ctx.Value("logged").(bool)
 934+	return ok && t
 935+}
 936+
 937 // Page prints a page implementing Page interface.
 938 
 939-//line templates/base.qtpl:39
 940-func StreamPageTemplate(qw422016 *qt422016.Writer, p Page) {
 941-//line templates/base.qtpl:39
 942+//line templates/base.qtpl:48
 943+func StreamPageTemplate(qw422016 *qt422016.Writer, p Page, ctx context.Context) {
 944+//line templates/base.qtpl:48
 945 	qw422016.N().S(`
 946 <!DOCTYPE html>
 947 <html lang="en">
 948@@ -88,64 +97,64 @@   <head>
 949     <meta charset="utf-8">
 950     <link rel="icon" href="data:,">
 951     <title>`)
 952-//line templates/base.qtpl:45
 953-	p.StreamTitle(qw422016)
 954-//line templates/base.qtpl:45
 955+//line templates/base.qtpl:54
 956+	p.StreamTitle(qw422016, ctx)
 957+//line templates/base.qtpl:54
 958 	qw422016.N().S(`</title> 
 959     <link rel="stylesheet" href="/static/main`)
 960-//line templates/base.qtpl:46
 961+//line templates/base.qtpl:55
 962 	qw422016.E().S(Slug)
 963-//line templates/base.qtpl:46
 964+//line templates/base.qtpl:55
 965 	qw422016.N().S(`.css">
 966     <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
 967     <meta name="viewport" content="width=device-width, initial-scale=1" />
 968   </head>
 969   <body>
 970     `)
 971-//line templates/base.qtpl:51
 972-	p.StreamNavbar(qw422016)
 973-//line templates/base.qtpl:51
 974+//line templates/base.qtpl:60
 975+	p.StreamNavbar(qw422016, ctx)
 976+//line templates/base.qtpl:60
 977 	qw422016.N().S(`
 978     <div class="container">
 979       `)
 980-//line templates/base.qtpl:53
 981-	p.StreamContent(qw422016)
 982-//line templates/base.qtpl:53
 983+//line templates/base.qtpl:62
 984+	p.StreamContent(qw422016, ctx)
 985+//line templates/base.qtpl:62
 986 	qw422016.N().S(`
 987     </div>
 988   </body>
 989   `)
 990-//line templates/base.qtpl:56
 991-	p.StreamScript(qw422016)
 992-//line templates/base.qtpl:56
 993+//line templates/base.qtpl:65
 994+	p.StreamScript(qw422016, ctx)
 995+//line templates/base.qtpl:65
 996 	qw422016.N().S(`
 997 </html>
 998 `)
 999-//line templates/base.qtpl:58
1000+//line templates/base.qtpl:67
1001 }
1002 
1003-//line templates/base.qtpl:58
1004-func WritePageTemplate(qq422016 qtio422016.Writer, p Page) {
1005-//line templates/base.qtpl:58
1006+//line templates/base.qtpl:67
1007+func WritePageTemplate(qq422016 qtio422016.Writer, p Page, ctx context.Context) {
1008+//line templates/base.qtpl:67
1009 	qw422016 := qt422016.AcquireWriter(qq422016)
1010-//line templates/base.qtpl:58
1011-	StreamPageTemplate(qw422016, p)
1012-//line templates/base.qtpl:58
1013+//line templates/base.qtpl:67
1014+	StreamPageTemplate(qw422016, p, ctx)
1015+//line templates/base.qtpl:67
1016 	qt422016.ReleaseWriter(qw422016)
1017-//line templates/base.qtpl:58
1018+//line templates/base.qtpl:67
1019 }
1020 
1021-//line templates/base.qtpl:58
1022-func PageTemplate(p Page) string {
1023-//line templates/base.qtpl:58
1024+//line templates/base.qtpl:67
1025+func PageTemplate(p Page, ctx context.Context) string {
1026+//line templates/base.qtpl:67
1027 	qb422016 := qt422016.AcquireByteBuffer()
1028-//line templates/base.qtpl:58
1029-	WritePageTemplate(qb422016, p)
1030-//line templates/base.qtpl:58
1031+//line templates/base.qtpl:67
1032+	WritePageTemplate(qb422016, p, ctx)
1033+//line templates/base.qtpl:67
1034 	qs422016 := string(qb422016.B)
1035-//line templates/base.qtpl:58
1036+//line templates/base.qtpl:67
1037 	qt422016.ReleaseByteBuffer(qb422016)
1038-//line templates/base.qtpl:58
1039+//line templates/base.qtpl:67
1040 	return qs422016
1041-//line templates/base.qtpl:58
1042+//line templates/base.qtpl:67
1043 }
1044diff --git a/templates/error.qtpl b/templates/error.qtpl
1045index 771d5333f95ee509b2e1680de168003bf24b2b32..65e1321d87ff4dbe30790880e238fc7f5ce5dd14 100644
1046--- a/templates/error.qtpl
1047+++ b/templates/error.qtpl
1048@@ -1,16 +1,18 @@
1049+{% import "context" %}
1050+
1051 {% code
1052 type ErrorPage struct {
1053     Message string
1054 }
1055 %}
1056 
1057-{% func (p *ErrorPage) Title() %}Error{% endfunc %}
1058+{% func (p *ErrorPage) Title(ctx context.Context) %}Error{% endfunc %}
1059 
1060-{% func (p *ErrorPage) Navbar() %}{%= Navbar(Git) %}{% endfunc %}
1061+{% func (p *ErrorPage) Navbar(ctx context.Context) %}{%= Navbar(ctx, Git) %}{% endfunc %}
1062 
1063-{% func (p *ErrorPage) Content() %}
1064+{% func (p *ErrorPage) Content(ctx context.Context) %}
1065 {%s p.Message %}
1066 {% endfunc %}
1067 
1068-{% func (p *ErrorPage) Script() %}
1069+{% func (p *ErrorPage) Script(ctx context.Context) %}
1070 {% endfunc %}
1071diff --git a/templates/error.qtpl.go b/templates/error.qtpl.go
1072index f415968c0d258de095a65790f09956cbf2976396..d4732c525cc879e2ac39ad7b46b481fa35edd748 100644
1073--- a/templates/error.qtpl.go
1074+++ b/templates/error.qtpl.go
1075@@ -5,80 +5,50 @@ //line templates/error.qtpl:1
1076 package templates
1077 
1078 //line templates/error.qtpl:1
1079+import "context"
1080+
1081+//line templates/error.qtpl:3
1082 import (
1083 	qtio422016 "io"
1084 
1085 	qt422016 "github.com/valyala/quicktemplate"
1086 )
1087 
1088-//line templates/error.qtpl:1
1089+//line templates/error.qtpl:3
1090 var (
1091 	_ = qtio422016.Copy
1092 	_ = qt422016.AcquireByteBuffer
1093 )
1094 
1095-//line templates/error.qtpl:2
1096+//line templates/error.qtpl:4
1097 type ErrorPage struct {
1098 	Message string
1099 }
1100 
1101-//line templates/error.qtpl:7
1102-func (p *ErrorPage) StreamTitle(qw422016 *qt422016.Writer) {
1103-//line templates/error.qtpl:7
1104-	qw422016.N().S(`Error`)
1105-//line templates/error.qtpl:7
1106-}
1107-
1108-//line templates/error.qtpl:7
1109-func (p *ErrorPage) WriteTitle(qq422016 qtio422016.Writer) {
1110-//line templates/error.qtpl:7
1111-	qw422016 := qt422016.AcquireWriter(qq422016)
1112-//line templates/error.qtpl:7
1113-	p.StreamTitle(qw422016)
1114-//line templates/error.qtpl:7
1115-	qt422016.ReleaseWriter(qw422016)
1116-//line templates/error.qtpl:7
1117-}
1118-
1119-//line templates/error.qtpl:7
1120-func (p *ErrorPage) Title() string {
1121-//line templates/error.qtpl:7
1122-	qb422016 := qt422016.AcquireByteBuffer()
1123-//line templates/error.qtpl:7
1124-	p.WriteTitle(qb422016)
1125-//line templates/error.qtpl:7
1126-	qs422016 := string(qb422016.B)
1127-//line templates/error.qtpl:7
1128-	qt422016.ReleaseByteBuffer(qb422016)
1129-//line templates/error.qtpl:7
1130-	return qs422016
1131-//line templates/error.qtpl:7
1132-}
1133-
1134 //line templates/error.qtpl:9
1135-func (p *ErrorPage) StreamNavbar(qw422016 *qt422016.Writer) {
1136+func (p *ErrorPage) StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) {
1137 //line templates/error.qtpl:9
1138-	StreamNavbar(qw422016, Git)
1139+	qw422016.N().S(`Error`)
1140 //line templates/error.qtpl:9
1141 }
1142 
1143 //line templates/error.qtpl:9
1144-func (p *ErrorPage) WriteNavbar(qq422016 qtio422016.Writer) {
1145+func (p *ErrorPage) WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) {
1146 //line templates/error.qtpl:9
1147 	qw422016 := qt422016.AcquireWriter(qq422016)
1148 //line templates/error.qtpl:9
1149-	p.StreamNavbar(qw422016)
1150+	p.StreamTitle(qw422016, ctx)
1151 //line templates/error.qtpl:9
1152 	qt422016.ReleaseWriter(qw422016)
1153 //line templates/error.qtpl:9
1154 }
1155 
1156 //line templates/error.qtpl:9
1157-func (p *ErrorPage) Navbar() string {
1158+func (p *ErrorPage) Title(ctx context.Context) string {
1159 //line templates/error.qtpl:9
1160 	qb422016 := qt422016.AcquireByteBuffer()
1161 //line templates/error.qtpl:9
1162-	p.WriteNavbar(qb422016)
1163+	p.WriteTitle(qb422016, ctx)
1164 //line templates/error.qtpl:9
1165 	qs422016 := string(qb422016.B)
1166 //line templates/error.qtpl:9
1167@@ -89,74 +59,107 @@ //line templates/error.qtpl:9
1168 }
1169 
1170 //line templates/error.qtpl:11
1171-func (p *ErrorPage) StreamContent(qw422016 *qt422016.Writer) {
1172+func (p *ErrorPage) StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) {
1173+//line templates/error.qtpl:11
1174+	StreamNavbar(qw422016, ctx, Git)
1175+//line templates/error.qtpl:11
1176+}
1177+
1178+//line templates/error.qtpl:11
1179+func (p *ErrorPage) WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) {
1180+//line templates/error.qtpl:11
1181+	qw422016 := qt422016.AcquireWriter(qq422016)
1182+//line templates/error.qtpl:11
1183+	p.StreamNavbar(qw422016, ctx)
1184+//line templates/error.qtpl:11
1185+	qt422016.ReleaseWriter(qw422016)
1186 //line templates/error.qtpl:11
1187+}
1188+
1189+//line templates/error.qtpl:11
1190+func (p *ErrorPage) Navbar(ctx context.Context) string {
1191+//line templates/error.qtpl:11
1192+	qb422016 := qt422016.AcquireByteBuffer()
1193+//line templates/error.qtpl:11
1194+	p.WriteNavbar(qb422016, ctx)
1195+//line templates/error.qtpl:11
1196+	qs422016 := string(qb422016.B)
1197+//line templates/error.qtpl:11
1198+	qt422016.ReleaseByteBuffer(qb422016)
1199+//line templates/error.qtpl:11
1200+	return qs422016
1201+//line templates/error.qtpl:11
1202+}
1203+
1204+//line templates/error.qtpl:13
1205+func (p *ErrorPage) StreamContent(qw422016 *qt422016.Writer, ctx context.Context) {
1206+//line templates/error.qtpl:13
1207 	qw422016.N().S(`
1208 `)
1209-//line templates/error.qtpl:12
1210+//line templates/error.qtpl:14
1211 	qw422016.E().S(p.Message)
1212-//line templates/error.qtpl:12
1213+//line templates/error.qtpl:14
1214 	qw422016.N().S(`
1215 `)
1216-//line templates/error.qtpl:13
1217+//line templates/error.qtpl:15
1218 }
1219 
1220-//line templates/error.qtpl:13
1221-func (p *ErrorPage) WriteContent(qq422016 qtio422016.Writer) {
1222-//line templates/error.qtpl:13
1223+//line templates/error.qtpl:15
1224+func (p *ErrorPage) WriteContent(qq422016 qtio422016.Writer, ctx context.Context) {
1225+//line templates/error.qtpl:15
1226 	qw422016 := qt422016.AcquireWriter(qq422016)
1227-//line templates/error.qtpl:13
1228-	p.StreamContent(qw422016)
1229-//line templates/error.qtpl:13
1230+//line templates/error.qtpl:15
1231+	p.StreamContent(qw422016, ctx)
1232+//line templates/error.qtpl:15
1233 	qt422016.ReleaseWriter(qw422016)
1234-//line templates/error.qtpl:13
1235+//line templates/error.qtpl:15
1236 }
1237 
1238-//line templates/error.qtpl:13
1239-func (p *ErrorPage) Content() string {
1240-//line templates/error.qtpl:13
1241+//line templates/error.qtpl:15
1242+func (p *ErrorPage) Content(ctx context.Context) string {
1243+//line templates/error.qtpl:15
1244 	qb422016 := qt422016.AcquireByteBuffer()
1245-//line templates/error.qtpl:13
1246-	p.WriteContent(qb422016)
1247-//line templates/error.qtpl:13
1248+//line templates/error.qtpl:15
1249+	p.WriteContent(qb422016, ctx)
1250+//line templates/error.qtpl:15
1251 	qs422016 := string(qb422016.B)
1252-//line templates/error.qtpl:13
1253+//line templates/error.qtpl:15
1254 	qt422016.ReleaseByteBuffer(qb422016)
1255-//line templates/error.qtpl:13
1256+//line templates/error.qtpl:15
1257 	return qs422016
1258-//line templates/error.qtpl:13
1259+//line templates/error.qtpl:15
1260 }
1261 
1262-//line templates/error.qtpl:15
1263-func (p *ErrorPage) StreamScript(qw422016 *qt422016.Writer) {
1264-//line templates/error.qtpl:15
1265+//line templates/error.qtpl:17
1266+func (p *ErrorPage) StreamScript(qw422016 *qt422016.Writer, ctx context.Context) {
1267+//line templates/error.qtpl:17
1268 	qw422016.N().S(`
1269 `)
1270-//line templates/error.qtpl:16
1271+//line templates/error.qtpl:18
1272 }
1273 
1274-//line templates/error.qtpl:16
1275-func (p *ErrorPage) WriteScript(qq422016 qtio422016.Writer) {
1276-//line templates/error.qtpl:16
1277+//line templates/error.qtpl:18
1278+func (p *ErrorPage) WriteScript(qq422016 qtio422016.Writer, ctx context.Context) {
1279+//line templates/error.qtpl:18
1280 	qw422016 := qt422016.AcquireWriter(qq422016)
1281-//line templates/error.qtpl:16
1282-	p.StreamScript(qw422016)
1283-//line templates/error.qtpl:16
1284+//line templates/error.qtpl:18
1285+	p.StreamScript(qw422016, ctx)
1286+//line templates/error.qtpl:18
1287 	qt422016.ReleaseWriter(qw422016)
1288-//line templates/error.qtpl:16
1289+//line templates/error.qtpl:18
1290 }
1291 
1292-//line templates/error.qtpl:16
1293-func (p *ErrorPage) Script() string {
1294-//line templates/error.qtpl:16
1295+//line templates/error.qtpl:18
1296+func (p *ErrorPage) Script(ctx context.Context) string {
1297+//line templates/error.qtpl:18
1298 	qb422016 := qt422016.AcquireByteBuffer()
1299-//line templates/error.qtpl:16
1300-	p.WriteScript(qb422016)
1301-//line templates/error.qtpl:16
1302+//line templates/error.qtpl:18
1303+	p.WriteScript(qb422016, ctx)
1304+//line templates/error.qtpl:18
1305 	qs422016 := string(qb422016.B)
1306-//line templates/error.qtpl:16
1307+//line templates/error.qtpl:18
1308 	qt422016.ReleaseByteBuffer(qb422016)
1309-//line templates/error.qtpl:16
1310+//line templates/error.qtpl:18
1311 	return qs422016
1312-//line templates/error.qtpl:16
1313+//line templates/error.qtpl:18
1314 }
1315diff --git a/templates/gititem.qtpl b/templates/gititem.qtpl
1316index a6a312d333e2e4942004073ed33494454f1eeeea..c43bbfdf906e81797b9bbbc00e8dab843f960be0 100644
1317--- a/templates/gititem.qtpl
1318+++ b/templates/gititem.qtpl
1319@@ -1,3 +1,5 @@
1320+{% import "context" %}
1321+
1322 {% interface 
1323 GitItemBase {
1324    Nav(name, ref string)
1325@@ -13,16 +15,16 @@     GitItemBase
1326 }
1327 %}
1328 
1329-{% func (p *GitItemPage) Title() %}Git | {%s p.Name %}{% endfunc %}
1330+{% func (p *GitItemPage) Title(ctx context.Context) %}Git | {%s p.Name %}{% endfunc %}
1331 
1332-{% func (p *GitItemPage) Navbar() %}{%= Navbar(Git) %}{% endfunc %}
1333+{% func (p *GitItemPage) Navbar(ctx context.Context) %}{%= Navbar(ctx, Git) %}{% endfunc %}
1334 
1335-{% func (p *GitItemPage) Content() %}
1336+{% func (p *GitItemPage) Content(ctx context.Context) %}
1337 {%= p.Nav(p.Name, p.Ref) %}
1338 <div class="container">
1339 {%= p.GitContent(p.Name, p.Ref) %}
1340 </div>
1341 {% endfunc %}
1342 
1343-{% func (p *GitItemPage) Script() %}
1344+{% func (p *GitItemPage) Script(ctx context.Context) %}
1345 {% endfunc %}
1346diff --git a/templates/gititem.qtpl.go b/templates/gititem.qtpl.go
1347index c9a00b29d0fd28cd514db8e97d63f5dcff9b172b..5292e44812ac71d251a8678380da7c80b40e48e8 100644
1348--- a/templates/gititem.qtpl.go
1349+++ b/templates/gititem.qtpl.go
1350@@ -5,101 +5,71 @@ //line templates/gititem.qtpl:1
1351 package templates
1352 
1353 //line templates/gititem.qtpl:1
1354+import "context"
1355+
1356+//line templates/gititem.qtpl:3
1357 import (
1358 	qtio422016 "io"
1359 
1360 	qt422016 "github.com/valyala/quicktemplate"
1361 )
1362 
1363-//line templates/gititem.qtpl:1
1364+//line templates/gititem.qtpl:3
1365 var (
1366 	_ = qtio422016.Copy
1367 	_ = qt422016.AcquireByteBuffer
1368 )
1369 
1370-//line templates/gititem.qtpl:2
1371+//line templates/gititem.qtpl:4
1372 type GitItemBase interface {
1373-//line templates/gititem.qtpl:2
1374+//line templates/gititem.qtpl:4
1375 	Nav(name, ref string) string
1376-//line templates/gititem.qtpl:2
1377+//line templates/gititem.qtpl:4
1378 	StreamNav(qw422016 *qt422016.Writer, name, ref string)
1379-//line templates/gititem.qtpl:2
1380+//line templates/gititem.qtpl:4
1381 	WriteNav(qq422016 qtio422016.Writer, name, ref string)
1382-//line templates/gititem.qtpl:2
1383+//line templates/gititem.qtpl:4
1384 	GitContent(name, ref string) string
1385-//line templates/gititem.qtpl:2
1386+//line templates/gititem.qtpl:4
1387 	StreamGitContent(qw422016 *qt422016.Writer, name, ref string)
1388-//line templates/gititem.qtpl:2
1389+//line templates/gititem.qtpl:4
1390 	WriteGitContent(qq422016 qtio422016.Writer, name, ref string)
1391-//line templates/gititem.qtpl:2
1392+//line templates/gititem.qtpl:4
1393 }
1394 
1395-//line templates/gititem.qtpl:9
1396+//line templates/gititem.qtpl:11
1397 type GitItemPage struct {
1398 	Name string
1399 	Ref  string
1400 	GitItemBase
1401 }
1402 
1403-//line templates/gititem.qtpl:16
1404-func (p *GitItemPage) StreamTitle(qw422016 *qt422016.Writer) {
1405-//line templates/gititem.qtpl:16
1406+//line templates/gititem.qtpl:18
1407+func (p *GitItemPage) StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) {
1408+//line templates/gititem.qtpl:18
1409 	qw422016.N().S(`Git | `)
1410-//line templates/gititem.qtpl:16
1411+//line templates/gititem.qtpl:18
1412 	qw422016.E().S(p.Name)
1413-//line templates/gititem.qtpl:16
1414-}
1415-
1416-//line templates/gititem.qtpl:16
1417-func (p *GitItemPage) WriteTitle(qq422016 qtio422016.Writer) {
1418-//line templates/gititem.qtpl:16
1419-	qw422016 := qt422016.AcquireWriter(qq422016)
1420-//line templates/gititem.qtpl:16
1421-	p.StreamTitle(qw422016)
1422-//line templates/gititem.qtpl:16
1423-	qt422016.ReleaseWriter(qw422016)
1424-//line templates/gititem.qtpl:16
1425-}
1426-
1427-//line templates/gititem.qtpl:16
1428-func (p *GitItemPage) Title() string {
1429-//line templates/gititem.qtpl:16
1430-	qb422016 := qt422016.AcquireByteBuffer()
1431-//line templates/gititem.qtpl:16
1432-	p.WriteTitle(qb422016)
1433-//line templates/gititem.qtpl:16
1434-	qs422016 := string(qb422016.B)
1435-//line templates/gititem.qtpl:16
1436-	qt422016.ReleaseByteBuffer(qb422016)
1437-//line templates/gititem.qtpl:16
1438-	return qs422016
1439-//line templates/gititem.qtpl:16
1440-}
1441-
1442-//line templates/gititem.qtpl:18
1443-func (p *GitItemPage) StreamNavbar(qw422016 *qt422016.Writer) {
1444-//line templates/gititem.qtpl:18
1445-	StreamNavbar(qw422016, Git)
1446 //line templates/gititem.qtpl:18
1447 }
1448 
1449 //line templates/gititem.qtpl:18
1450-func (p *GitItemPage) WriteNavbar(qq422016 qtio422016.Writer) {
1451+func (p *GitItemPage) WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) {
1452 //line templates/gititem.qtpl:18
1453 	qw422016 := qt422016.AcquireWriter(qq422016)
1454 //line templates/gititem.qtpl:18
1455-	p.StreamNavbar(qw422016)
1456+	p.StreamTitle(qw422016, ctx)
1457 //line templates/gititem.qtpl:18
1458 	qt422016.ReleaseWriter(qw422016)
1459 //line templates/gititem.qtpl:18
1460 }
1461 
1462 //line templates/gititem.qtpl:18
1463-func (p *GitItemPage) Navbar() string {
1464+func (p *GitItemPage) Title(ctx context.Context) string {
1465 //line templates/gititem.qtpl:18
1466 	qb422016 := qt422016.AcquireByteBuffer()
1467 //line templates/gititem.qtpl:18
1468-	p.WriteNavbar(qb422016)
1469+	p.WriteTitle(qb422016, ctx)
1470 //line templates/gititem.qtpl:18
1471 	qs422016 := string(qb422016.B)
1472 //line templates/gititem.qtpl:18
1473@@ -110,81 +80,114 @@ //line templates/gititem.qtpl:18
1474 }
1475 
1476 //line templates/gititem.qtpl:20
1477-func (p *GitItemPage) StreamContent(qw422016 *qt422016.Writer) {
1478+func (p *GitItemPage) StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) {
1479+//line templates/gititem.qtpl:20
1480+	StreamNavbar(qw422016, ctx, Git)
1481+//line templates/gititem.qtpl:20
1482+}
1483+
1484+//line templates/gititem.qtpl:20
1485+func (p *GitItemPage) WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) {
1486+//line templates/gititem.qtpl:20
1487+	qw422016 := qt422016.AcquireWriter(qq422016)
1488+//line templates/gititem.qtpl:20
1489+	p.StreamNavbar(qw422016, ctx)
1490+//line templates/gititem.qtpl:20
1491+	qt422016.ReleaseWriter(qw422016)
1492+//line templates/gititem.qtpl:20
1493+}
1494+
1495+//line templates/gititem.qtpl:20
1496+func (p *GitItemPage) Navbar(ctx context.Context) string {
1497+//line templates/gititem.qtpl:20
1498+	qb422016 := qt422016.AcquireByteBuffer()
1499+//line templates/gititem.qtpl:20
1500+	p.WriteNavbar(qb422016, ctx)
1501+//line templates/gititem.qtpl:20
1502+	qs422016 := string(qb422016.B)
1503 //line templates/gititem.qtpl:20
1504+	qt422016.ReleaseByteBuffer(qb422016)
1505+//line templates/gititem.qtpl:20
1506+	return qs422016
1507+//line templates/gititem.qtpl:20
1508+}
1509+
1510+//line templates/gititem.qtpl:22
1511+func (p *GitItemPage) StreamContent(qw422016 *qt422016.Writer, ctx context.Context) {
1512+//line templates/gititem.qtpl:22
1513 	qw422016.N().S(`
1514 `)
1515-//line templates/gititem.qtpl:21
1516+//line templates/gititem.qtpl:23
1517 	p.StreamNav(qw422016, p.Name, p.Ref)
1518-//line templates/gititem.qtpl:21
1519+//line templates/gititem.qtpl:23
1520 	qw422016.N().S(`
1521 <div class="container">
1522 `)
1523-//line templates/gititem.qtpl:23
1524+//line templates/gititem.qtpl:25
1525 	p.StreamGitContent(qw422016, p.Name, p.Ref)
1526-//line templates/gititem.qtpl:23
1527+//line templates/gititem.qtpl:25
1528 	qw422016.N().S(`
1529 </div>
1530 `)
1531-//line templates/gititem.qtpl:25
1532+//line templates/gititem.qtpl:27
1533 }
1534 
1535-//line templates/gititem.qtpl:25
1536-func (p *GitItemPage) WriteContent(qq422016 qtio422016.Writer) {
1537-//line templates/gititem.qtpl:25
1538+//line templates/gititem.qtpl:27
1539+func (p *GitItemPage) WriteContent(qq422016 qtio422016.Writer, ctx context.Context) {
1540+//line templates/gititem.qtpl:27
1541 	qw422016 := qt422016.AcquireWriter(qq422016)
1542-//line templates/gititem.qtpl:25
1543-	p.StreamContent(qw422016)
1544-//line templates/gititem.qtpl:25
1545+//line templates/gititem.qtpl:27
1546+	p.StreamContent(qw422016, ctx)
1547+//line templates/gititem.qtpl:27
1548 	qt422016.ReleaseWriter(qw422016)
1549-//line templates/gititem.qtpl:25
1550+//line templates/gititem.qtpl:27
1551 }
1552 
1553-//line templates/gititem.qtpl:25
1554-func (p *GitItemPage) Content() string {
1555-//line templates/gititem.qtpl:25
1556+//line templates/gititem.qtpl:27
1557+func (p *GitItemPage) Content(ctx context.Context) string {
1558+//line templates/gititem.qtpl:27
1559 	qb422016 := qt422016.AcquireByteBuffer()
1560-//line templates/gititem.qtpl:25
1561-	p.WriteContent(qb422016)
1562-//line templates/gititem.qtpl:25
1563+//line templates/gititem.qtpl:27
1564+	p.WriteContent(qb422016, ctx)
1565+//line templates/gititem.qtpl:27
1566 	qs422016 := string(qb422016.B)
1567-//line templates/gititem.qtpl:25
1568+//line templates/gititem.qtpl:27
1569 	qt422016.ReleaseByteBuffer(qb422016)
1570-//line templates/gititem.qtpl:25
1571+//line templates/gititem.qtpl:27
1572 	return qs422016
1573-//line templates/gititem.qtpl:25
1574+//line templates/gititem.qtpl:27
1575 }
1576 
1577-//line templates/gititem.qtpl:27
1578-func (p *GitItemPage) StreamScript(qw422016 *qt422016.Writer) {
1579-//line templates/gititem.qtpl:27
1580+//line templates/gititem.qtpl:29
1581+func (p *GitItemPage) StreamScript(qw422016 *qt422016.Writer, ctx context.Context) {
1582+//line templates/gititem.qtpl:29
1583 	qw422016.N().S(`
1584 `)
1585-//line templates/gititem.qtpl:28
1586+//line templates/gititem.qtpl:30
1587 }
1588 
1589-//line templates/gititem.qtpl:28
1590-func (p *GitItemPage) WriteScript(qq422016 qtio422016.Writer) {
1591-//line templates/gititem.qtpl:28
1592+//line templates/gititem.qtpl:30
1593+func (p *GitItemPage) WriteScript(qq422016 qtio422016.Writer, ctx context.Context) {
1594+//line templates/gititem.qtpl:30
1595 	qw422016 := qt422016.AcquireWriter(qq422016)
1596-//line templates/gititem.qtpl:28
1597-	p.StreamScript(qw422016)
1598-//line templates/gititem.qtpl:28
1599+//line templates/gititem.qtpl:30
1600+	p.StreamScript(qw422016, ctx)
1601+//line templates/gititem.qtpl:30
1602 	qt422016.ReleaseWriter(qw422016)
1603-//line templates/gititem.qtpl:28
1604+//line templates/gititem.qtpl:30
1605 }
1606 
1607-//line templates/gititem.qtpl:28
1608-func (p *GitItemPage) Script() string {
1609-//line templates/gititem.qtpl:28
1610+//line templates/gititem.qtpl:30
1611+func (p *GitItemPage) Script(ctx context.Context) string {
1612+//line templates/gititem.qtpl:30
1613 	qb422016 := qt422016.AcquireByteBuffer()
1614-//line templates/gititem.qtpl:28
1615-	p.WriteScript(qb422016)
1616-//line templates/gititem.qtpl:28
1617+//line templates/gititem.qtpl:30
1618+	p.WriteScript(qb422016, ctx)
1619+//line templates/gititem.qtpl:30
1620 	qs422016 := string(qb422016.B)
1621-//line templates/gititem.qtpl:28
1622+//line templates/gititem.qtpl:30
1623 	qt422016.ReleaseByteBuffer(qb422016)
1624-//line templates/gititem.qtpl:28
1625+//line templates/gititem.qtpl:30
1626 	return qs422016
1627-//line templates/gititem.qtpl:28
1628+//line templates/gititem.qtpl:30
1629 }
1630diff --git a/templates/gitlist.qtpl b/templates/gitlist.qtpl
1631index 7c104ca5b394315b2657d598ddd79f797af27de7..fa470089e9f4fd64c066822ec1c849628ddd4f1e 100644
1632--- a/templates/gitlist.qtpl
1633+++ b/templates/gitlist.qtpl
1634@@ -1,4 +1,5 @@
1635 {% import "git.gabrielgio.me/cerrado/pkg/service" %}
1636+{% import "context" %}
1637 
1638 {% code
1639 type GitListPage struct {
1640@@ -7,11 +8,11 @@     About []byte
1641 }
1642 %}
1643 
1644-{% func (p *GitListPage) Title() %}Git | List{% endfunc %}
1645+{% func (p *GitListPage) Title(ctx context.Context) %}Git | List{% endfunc %}
1646 
1647-{% func (p *GitListPage) Navbar() %}{%= Navbar(Git) %}{% endfunc %}
1648+{% func (p *GitListPage) Navbar(ctx context.Context) %}{%= Navbar(ctx, Git) %}{% endfunc %}
1649 
1650-{% func (p *GitListPage) Content() %}
1651+{% func (p *GitListPage) Content(ctx context.Context) %}
1652 <div class="row">
1653   <div class="col-md-6 order-last order-md-first">
1654     <div class="event-list">
1655@@ -40,5 +41,5 @@   </div>
1656 </div>
1657 {% endfunc %}
1658 
1659-{% func (p *GitListPage) Script() %}
1660+{% func (p *GitListPage) Script(ctx context.Context) %}
1661 {% endfunc %}
1662diff --git a/templates/gitlist.qtpl.go b/templates/gitlist.qtpl.go
1663index 29adbc6d54b1d1bc78b24b34043d756ee96e5d81..601dbb2fa8c947e791800ebded6aabbd64005919 100644
1664--- a/templates/gitlist.qtpl.go
1665+++ b/templates/gitlist.qtpl.go
1666@@ -7,159 +7,162 @@
1667 //line templates/gitlist.qtpl:1
1668 import "git.gabrielgio.me/cerrado/pkg/service"
1669 
1670-//line templates/gitlist.qtpl:3
1671+//line templates/gitlist.qtpl:2
1672+import "context"
1673+
1674+//line templates/gitlist.qtpl:4
1675 import (
1676 	qtio422016 "io"
1677 
1678 	qt422016 "github.com/valyala/quicktemplate"
1679 )
1680 
1681-//line templates/gitlist.qtpl:3
1682+//line templates/gitlist.qtpl:4
1683 var (
1684 	_ = qtio422016.Copy
1685 	_ = qt422016.AcquireByteBuffer
1686 )
1687 
1688-//line templates/gitlist.qtpl:4
1689+//line templates/gitlist.qtpl:5
1690 type GitListPage struct {
1691 	Respositories []*service.Repository
1692 	About         []byte
1693 }
1694 
1695-//line templates/gitlist.qtpl:10
1696-func (p *GitListPage) StreamTitle(qw422016 *qt422016.Writer) {
1697-//line templates/gitlist.qtpl:10
1698+//line templates/gitlist.qtpl:11
1699+func (p *GitListPage) StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) {
1700+//line templates/gitlist.qtpl:11
1701 	qw422016.N().S(`Git | List`)
1702-//line templates/gitlist.qtpl:10
1703+//line templates/gitlist.qtpl:11
1704 }
1705 
1706-//line templates/gitlist.qtpl:10
1707-func (p *GitListPage) WriteTitle(qq422016 qtio422016.Writer) {
1708-//line templates/gitlist.qtpl:10
1709+//line templates/gitlist.qtpl:11
1710+func (p *GitListPage) WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) {
1711+//line templates/gitlist.qtpl:11
1712 	qw422016 := qt422016.AcquireWriter(qq422016)
1713-//line templates/gitlist.qtpl:10
1714-	p.StreamTitle(qw422016)
1715-//line templates/gitlist.qtpl:10
1716+//line templates/gitlist.qtpl:11
1717+	p.StreamTitle(qw422016, ctx)
1718+//line templates/gitlist.qtpl:11
1719 	qt422016.ReleaseWriter(qw422016)
1720-//line templates/gitlist.qtpl:10
1721+//line templates/gitlist.qtpl:11
1722 }
1723 
1724-//line templates/gitlist.qtpl:10
1725-func (p *GitListPage) Title() string {
1726-//line templates/gitlist.qtpl:10
1727+//line templates/gitlist.qtpl:11
1728+func (p *GitListPage) Title(ctx context.Context) string {
1729+//line templates/gitlist.qtpl:11
1730 	qb422016 := qt422016.AcquireByteBuffer()
1731-//line templates/gitlist.qtpl:10
1732-	p.WriteTitle(qb422016)
1733-//line templates/gitlist.qtpl:10
1734+//line templates/gitlist.qtpl:11
1735+	p.WriteTitle(qb422016, ctx)
1736+//line templates/gitlist.qtpl:11
1737 	qs422016 := string(qb422016.B)
1738-//line templates/gitlist.qtpl:10
1739+//line templates/gitlist.qtpl:11
1740 	qt422016.ReleaseByteBuffer(qb422016)
1741-//line templates/gitlist.qtpl:10
1742+//line templates/gitlist.qtpl:11
1743 	return qs422016
1744-//line templates/gitlist.qtpl:10
1745+//line templates/gitlist.qtpl:11
1746 }
1747 
1748-//line templates/gitlist.qtpl:12
1749-func (p *GitListPage) StreamNavbar(qw422016 *qt422016.Writer) {
1750-//line templates/gitlist.qtpl:12
1751-	StreamNavbar(qw422016, Git)
1752-//line templates/gitlist.qtpl:12
1753+//line templates/gitlist.qtpl:13
1754+func (p *GitListPage) StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) {
1755+//line templates/gitlist.qtpl:13
1756+	StreamNavbar(qw422016, ctx, Git)
1757+//line templates/gitlist.qtpl:13
1758 }
1759 
1760-//line templates/gitlist.qtpl:12
1761-func (p *GitListPage) WriteNavbar(qq422016 qtio422016.Writer) {
1762-//line templates/gitlist.qtpl:12
1763+//line templates/gitlist.qtpl:13
1764+func (p *GitListPage) WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) {
1765+//line templates/gitlist.qtpl:13
1766 	qw422016 := qt422016.AcquireWriter(qq422016)
1767-//line templates/gitlist.qtpl:12
1768-	p.StreamNavbar(qw422016)
1769-//line templates/gitlist.qtpl:12
1770+//line templates/gitlist.qtpl:13
1771+	p.StreamNavbar(qw422016, ctx)
1772+//line templates/gitlist.qtpl:13
1773 	qt422016.ReleaseWriter(qw422016)
1774-//line templates/gitlist.qtpl:12
1775+//line templates/gitlist.qtpl:13
1776 }
1777 
1778-//line templates/gitlist.qtpl:12
1779-func (p *GitListPage) Navbar() string {
1780-//line templates/gitlist.qtpl:12
1781+//line templates/gitlist.qtpl:13
1782+func (p *GitListPage) Navbar(ctx context.Context) string {
1783+//line templates/gitlist.qtpl:13
1784 	qb422016 := qt422016.AcquireByteBuffer()
1785-//line templates/gitlist.qtpl:12
1786-	p.WriteNavbar(qb422016)
1787-//line templates/gitlist.qtpl:12
1788+//line templates/gitlist.qtpl:13
1789+	p.WriteNavbar(qb422016, ctx)
1790+//line templates/gitlist.qtpl:13
1791 	qs422016 := string(qb422016.B)
1792-//line templates/gitlist.qtpl:12
1793+//line templates/gitlist.qtpl:13
1794 	qt422016.ReleaseByteBuffer(qb422016)
1795-//line templates/gitlist.qtpl:12
1796+//line templates/gitlist.qtpl:13
1797 	return qs422016
1798-//line templates/gitlist.qtpl:12
1799+//line templates/gitlist.qtpl:13
1800 }
1801 
1802-//line templates/gitlist.qtpl:14
1803-func (p *GitListPage) StreamContent(qw422016 *qt422016.Writer) {
1804-//line templates/gitlist.qtpl:14
1805+//line templates/gitlist.qtpl:15
1806+func (p *GitListPage) StreamContent(qw422016 *qt422016.Writer, ctx context.Context) {
1807+//line templates/gitlist.qtpl:15
1808 	qw422016.N().S(`
1809 <div class="row">
1810   <div class="col-md-6 order-last order-md-first">
1811     <div class="event-list">
1812       `)
1813-//line templates/gitlist.qtpl:18
1814+//line templates/gitlist.qtpl:19
1815 	for _, r := range p.Respositories {
1816-//line templates/gitlist.qtpl:18
1817+//line templates/gitlist.qtpl:19
1818 		qw422016.N().S(`
1819       <div class="event">
1820         <h4>
1821           <a href="/`)
1822-//line templates/gitlist.qtpl:21
1823+//line templates/gitlist.qtpl:22
1824 		qw422016.E().S(r.Name)
1825-//line templates/gitlist.qtpl:21
1826+//line templates/gitlist.qtpl:22
1827 		qw422016.N().S(`/">`)
1828-//line templates/gitlist.qtpl:21
1829+//line templates/gitlist.qtpl:22
1830 		qw422016.E().S(r.Name)
1831-//line templates/gitlist.qtpl:21
1832+//line templates/gitlist.qtpl:22
1833 		qw422016.N().S(`</a>
1834         </h4>
1835         </hr>
1836         <p>`)
1837-//line templates/gitlist.qtpl:24
1838+//line templates/gitlist.qtpl:25
1839 		qw422016.E().S(r.Description)
1840-//line templates/gitlist.qtpl:24
1841+//line templates/gitlist.qtpl:25
1842 		qw422016.N().S(`</p>
1843         <p>
1844           <a href="/`)
1845-//line templates/gitlist.qtpl:26
1846+//line templates/gitlist.qtpl:27
1847 		qw422016.E().S(r.Name)
1848-//line templates/gitlist.qtpl:26
1849+//line templates/gitlist.qtpl:27
1850 		qw422016.N().S(`/log/`)
1851-//line templates/gitlist.qtpl:26
1852+//line templates/gitlist.qtpl:27
1853 		qw422016.E().S(r.Ref)
1854-//line templates/gitlist.qtpl:26
1855+//line templates/gitlist.qtpl:27
1856 		qw422016.N().S(`/">log</a>
1857           <a href="/`)
1858-//line templates/gitlist.qtpl:27
1859+//line templates/gitlist.qtpl:28
1860 		qw422016.E().S(r.Name)
1861-//line templates/gitlist.qtpl:27
1862+//line templates/gitlist.qtpl:28
1863 		qw422016.N().S(`/tree/`)
1864-//line templates/gitlist.qtpl:27
1865+//line templates/gitlist.qtpl:28
1866 		qw422016.E().S(r.Ref)
1867-//line templates/gitlist.qtpl:27
1868+//line templates/gitlist.qtpl:28
1869 		qw422016.N().S(`/">tree</a>
1870           <a href="/`)
1871-//line templates/gitlist.qtpl:28
1872+//line templates/gitlist.qtpl:29
1873 		qw422016.E().S(r.Name)
1874-//line templates/gitlist.qtpl:28
1875+//line templates/gitlist.qtpl:29
1876 		qw422016.N().S(`/refs/">refs</a>
1877         </p>
1878       </div>
1879       `)
1880-//line templates/gitlist.qtpl:31
1881+//line templates/gitlist.qtpl:32
1882 	}
1883-//line templates/gitlist.qtpl:31
1884+//line templates/gitlist.qtpl:32
1885 	qw422016.N().S(`
1886     </div>
1887   </div>
1888   <div id="about" class="col-md-4 order-first order-md-last">
1889     `)
1890-//line templates/gitlist.qtpl:35
1891+//line templates/gitlist.qtpl:36
1892 	qw422016.N().Z(p.About)
1893-//line templates/gitlist.qtpl:35
1894+//line templates/gitlist.qtpl:36
1895 	qw422016.N().S(`
1896     <div class="alert alert-warning text-center" role="alert">
1897         This project is under development, things may be broken or incomplete.
1898@@ -167,65 +170,65 @@     </div>
1899   </div>
1900 </div>
1901 `)
1902-//line templates/gitlist.qtpl:41
1903+//line templates/gitlist.qtpl:42
1904 }
1905 
1906-//line templates/gitlist.qtpl:41
1907-func (p *GitListPage) WriteContent(qq422016 qtio422016.Writer) {
1908-//line templates/gitlist.qtpl:41
1909+//line templates/gitlist.qtpl:42
1910+func (p *GitListPage) WriteContent(qq422016 qtio422016.Writer, ctx context.Context) {
1911+//line templates/gitlist.qtpl:42
1912 	qw422016 := qt422016.AcquireWriter(qq422016)
1913-//line templates/gitlist.qtpl:41
1914-	p.StreamContent(qw422016)
1915-//line templates/gitlist.qtpl:41
1916+//line templates/gitlist.qtpl:42
1917+	p.StreamContent(qw422016, ctx)
1918+//line templates/gitlist.qtpl:42
1919 	qt422016.ReleaseWriter(qw422016)
1920-//line templates/gitlist.qtpl:41
1921+//line templates/gitlist.qtpl:42
1922 }
1923 
1924-//line templates/gitlist.qtpl:41
1925-func (p *GitListPage) Content() string {
1926-//line templates/gitlist.qtpl:41
1927+//line templates/gitlist.qtpl:42
1928+func (p *GitListPage) Content(ctx context.Context) string {
1929+//line templates/gitlist.qtpl:42
1930 	qb422016 := qt422016.AcquireByteBuffer()
1931-//line templates/gitlist.qtpl:41
1932-	p.WriteContent(qb422016)
1933-//line templates/gitlist.qtpl:41
1934+//line templates/gitlist.qtpl:42
1935+	p.WriteContent(qb422016, ctx)
1936+//line templates/gitlist.qtpl:42
1937 	qs422016 := string(qb422016.B)
1938-//line templates/gitlist.qtpl:41
1939+//line templates/gitlist.qtpl:42
1940 	qt422016.ReleaseByteBuffer(qb422016)
1941-//line templates/gitlist.qtpl:41
1942+//line templates/gitlist.qtpl:42
1943 	return qs422016
1944-//line templates/gitlist.qtpl:41
1945+//line templates/gitlist.qtpl:42
1946 }
1947 
1948-//line templates/gitlist.qtpl:43
1949-func (p *GitListPage) StreamScript(qw422016 *qt422016.Writer) {
1950-//line templates/gitlist.qtpl:43
1951+//line templates/gitlist.qtpl:44
1952+func (p *GitListPage) StreamScript(qw422016 *qt422016.Writer, ctx context.Context) {
1953+//line templates/gitlist.qtpl:44
1954 	qw422016.N().S(`
1955 `)
1956-//line templates/gitlist.qtpl:44
1957+//line templates/gitlist.qtpl:45
1958 }
1959 
1960-//line templates/gitlist.qtpl:44
1961-func (p *GitListPage) WriteScript(qq422016 qtio422016.Writer) {
1962-//line templates/gitlist.qtpl:44
1963+//line templates/gitlist.qtpl:45
1964+func (p *GitListPage) WriteScript(qq422016 qtio422016.Writer, ctx context.Context) {
1965+//line templates/gitlist.qtpl:45
1966 	qw422016 := qt422016.AcquireWriter(qq422016)
1967-//line templates/gitlist.qtpl:44
1968-	p.StreamScript(qw422016)
1969-//line templates/gitlist.qtpl:44
1970+//line templates/gitlist.qtpl:45
1971+	p.StreamScript(qw422016, ctx)
1972+//line templates/gitlist.qtpl:45
1973 	qt422016.ReleaseWriter(qw422016)
1974-//line templates/gitlist.qtpl:44
1975+//line templates/gitlist.qtpl:45
1976 }
1977 
1978-//line templates/gitlist.qtpl:44
1979-func (p *GitListPage) Script() string {
1980-//line templates/gitlist.qtpl:44
1981+//line templates/gitlist.qtpl:45
1982+func (p *GitListPage) Script(ctx context.Context) string {
1983+//line templates/gitlist.qtpl:45
1984 	qb422016 := qt422016.AcquireByteBuffer()
1985-//line templates/gitlist.qtpl:44
1986-	p.WriteScript(qb422016)
1987-//line templates/gitlist.qtpl:44
1988+//line templates/gitlist.qtpl:45
1989+	p.WriteScript(qb422016, ctx)
1990+//line templates/gitlist.qtpl:45
1991 	qs422016 := string(qb422016.B)
1992-//line templates/gitlist.qtpl:44
1993+//line templates/gitlist.qtpl:45
1994 	qt422016.ReleaseByteBuffer(qb422016)
1995-//line templates/gitlist.qtpl:44
1996+//line templates/gitlist.qtpl:45
1997 	return qs422016
1998-//line templates/gitlist.qtpl:44
1999+//line templates/gitlist.qtpl:45
2000 }
2001diff --git a/templates/login.qtpl b/templates/login.qtpl
2002index eee57117106360c2f0d9bbf104e0b820bf051e92..7815bd7401ff8cdee199144ac88b61421f253eb7 100644
2003--- a/templates/login.qtpl
2004+++ b/templates/login.qtpl
2005@@ -1,31 +1,41 @@
2006+{% import "context" %}
2007+
2008 {% code
2009 type LoginPage struct {
2010+    ErrorMessage string
2011 }
2012 %}
2013 
2014-{% func (p *LoginPage) Title() %}Hello{% endfunc %}
2015+{% func (p *LoginPage) Title(ctx context.Context) %}Hello{% endfunc %}
2016 
2017-{% func (p *LoginPage) Navbar() %}{%= Navbar(Login) %}{% endfunc %}
2018+{% func (p *LoginPage) Navbar(ctx context.Context) %}{%= Navbar(ctx, Login) %}{% endfunc %}
2019 
2020-{% func (p *LoginPage) Content() %}
2021+{% func (p *LoginPage) Content(ctx context.Context) %}
2022 <div class="row">
2023   <div class="col-md-6 offset-md-3">
2024-    <form>
2025+    <form action="/login/" method="POST">
2026       <div class="form-group m-3">
2027         <label for="username" class="form-label">Username</label>
2028-        <input type="text" class="form-control" id="username" aria-describedby="emailHelp">
2029+        <input type="text" class="form-control" name="username" id="username">
2030       </div>
2031       <div class="form-group m-3">
2032         <label for="password" class="form-label">Password</label>
2033-        <input type="password" class="form-control" id="password">
2034+        <input type="password" class="form-control" name="password" id="password">
2035       </div>
2036       <div class="form-group m-3">
2037         <button type="submit" class="btn btn-primary">Login</button>
2038       </div>
2039     </form>
2040   </div>
2041+  {% if p.ErrorMessage != "" %}
2042+  <div class="col-md-6 offset-md-3">
2043+    <div class="alert alert-warning text-center" >
2044+        {%s p.ErrorMessage  %}
2045+    </div>
2046+  </div>
2047+  {% endif %}
2048 </div>
2049 {% endfunc %}
2050 
2051-{% func (p *LoginPage) Script() %}
2052+{% func (p *LoginPage) Script(ctx context.Context) %}
2053 {% endfunc %}
2054diff --git a/templates/login.qtpl.go b/templates/login.qtpl.go
2055index 0d3d2b0c41d592cf030a0806131d1057e8726786..5c07a44eea42027c0c04ab1e56ec192c187964cb 100644
2056--- a/templates/login.qtpl.go
2057+++ b/templates/login.qtpl.go
2058@@ -5,169 +5,192 @@ //line templates/login.qtpl:1
2059 package templates
2060 
2061 //line templates/login.qtpl:1
2062+import "context"
2063+
2064+//line templates/login.qtpl:3
2065 import (
2066 	qtio422016 "io"
2067 
2068 	qt422016 "github.com/valyala/quicktemplate"
2069 )
2070 
2071-//line templates/login.qtpl:1
2072+//line templates/login.qtpl:3
2073 var (
2074 	_ = qtio422016.Copy
2075 	_ = qt422016.AcquireByteBuffer
2076 )
2077 
2078-//line templates/login.qtpl:2
2079+//line templates/login.qtpl:4
2080 type LoginPage struct {
2081+	ErrorMessage string
2082 }
2083 
2084-//line templates/login.qtpl:6
2085-func (p *LoginPage) StreamTitle(qw422016 *qt422016.Writer) {
2086-//line templates/login.qtpl:6
2087+//line templates/login.qtpl:9
2088+func (p *LoginPage) StreamTitle(qw422016 *qt422016.Writer, ctx context.Context) {
2089+//line templates/login.qtpl:9
2090 	qw422016.N().S(`Hello`)
2091-//line templates/login.qtpl:6
2092+//line templates/login.qtpl:9
2093 }
2094 
2095-//line templates/login.qtpl:6
2096-func (p *LoginPage) WriteTitle(qq422016 qtio422016.Writer) {
2097-//line templates/login.qtpl:6
2098+//line templates/login.qtpl:9
2099+func (p *LoginPage) WriteTitle(qq422016 qtio422016.Writer, ctx context.Context) {
2100+//line templates/login.qtpl:9
2101 	qw422016 := qt422016.AcquireWriter(qq422016)
2102-//line templates/login.qtpl:6
2103-	p.StreamTitle(qw422016)
2104-//line templates/login.qtpl:6
2105+//line templates/login.qtpl:9
2106+	p.StreamTitle(qw422016, ctx)
2107+//line templates/login.qtpl:9
2108 	qt422016.ReleaseWriter(qw422016)
2109-//line templates/login.qtpl:6
2110+//line templates/login.qtpl:9
2111 }
2112 
2113-//line templates/login.qtpl:6
2114-func (p *LoginPage) Title() string {
2115-//line templates/login.qtpl:6
2116+//line templates/login.qtpl:9
2117+func (p *LoginPage) Title(ctx context.Context) string {
2118+//line templates/login.qtpl:9
2119 	qb422016 := qt422016.AcquireByteBuffer()
2120-//line templates/login.qtpl:6
2121-	p.WriteTitle(qb422016)
2122-//line templates/login.qtpl:6
2123+//line templates/login.qtpl:9
2124+	p.WriteTitle(qb422016, ctx)
2125+//line templates/login.qtpl:9
2126 	qs422016 := string(qb422016.B)
2127-//line templates/login.qtpl:6
2128+//line templates/login.qtpl:9
2129 	qt422016.ReleaseByteBuffer(qb422016)
2130-//line templates/login.qtpl:6
2131+//line templates/login.qtpl:9
2132 	return qs422016
2133-//line templates/login.qtpl:6
2134+//line templates/login.qtpl:9
2135 }
2136 
2137-//line templates/login.qtpl:8
2138-func (p *LoginPage) StreamNavbar(qw422016 *qt422016.Writer) {
2139-//line templates/login.qtpl:8
2140-	StreamNavbar(qw422016, Login)
2141-//line templates/login.qtpl:8
2142+//line templates/login.qtpl:11
2143+func (p *LoginPage) StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context) {
2144+//line templates/login.qtpl:11
2145+	StreamNavbar(qw422016, ctx, Login)
2146+//line templates/login.qtpl:11
2147 }
2148 
2149-//line templates/login.qtpl:8
2150-func (p *LoginPage) WriteNavbar(qq422016 qtio422016.Writer) {
2151-//line templates/login.qtpl:8
2152+//line templates/login.qtpl:11
2153+func (p *LoginPage) WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context) {
2154+//line templates/login.qtpl:11
2155 	qw422016 := qt422016.AcquireWriter(qq422016)
2156-//line templates/login.qtpl:8
2157-	p.StreamNavbar(qw422016)
2158-//line templates/login.qtpl:8
2159+//line templates/login.qtpl:11
2160+	p.StreamNavbar(qw422016, ctx)
2161+//line templates/login.qtpl:11
2162 	qt422016.ReleaseWriter(qw422016)
2163-//line templates/login.qtpl:8
2164+//line templates/login.qtpl:11
2165 }
2166 
2167-//line templates/login.qtpl:8
2168-func (p *LoginPage) Navbar() string {
2169-//line templates/login.qtpl:8
2170+//line templates/login.qtpl:11
2171+func (p *LoginPage) Navbar(ctx context.Context) string {
2172+//line templates/login.qtpl:11
2173 	qb422016 := qt422016.AcquireByteBuffer()
2174-//line templates/login.qtpl:8
2175-	p.WriteNavbar(qb422016)
2176-//line templates/login.qtpl:8
2177+//line templates/login.qtpl:11
2178+	p.WriteNavbar(qb422016, ctx)
2179+//line templates/login.qtpl:11
2180 	qs422016 := string(qb422016.B)
2181-//line templates/login.qtpl:8
2182+//line templates/login.qtpl:11
2183 	qt422016.ReleaseByteBuffer(qb422016)
2184-//line templates/login.qtpl:8
2185+//line templates/login.qtpl:11
2186 	return qs422016
2187-//line templates/login.qtpl:8
2188+//line templates/login.qtpl:11
2189 }
2190 
2191-//line templates/login.qtpl:10
2192-func (p *LoginPage) StreamContent(qw422016 *qt422016.Writer) {
2193-//line templates/login.qtpl:10
2194+//line templates/login.qtpl:13
2195+func (p *LoginPage) StreamContent(qw422016 *qt422016.Writer, ctx context.Context) {
2196+//line templates/login.qtpl:13
2197 	qw422016.N().S(`
2198 <div class="row">
2199   <div class="col-md-6 offset-md-3">
2200-    <form>
2201+    <form action="/login/" method="POST">
2202       <div class="form-group m-3">
2203         <label for="username" class="form-label">Username</label>
2204-        <input type="text" class="form-control" id="username" aria-describedby="emailHelp">
2205+        <input type="text" class="form-control" name="username" id="username">
2206       </div>
2207       <div class="form-group m-3">
2208         <label for="password" class="form-label">Password</label>
2209-        <input type="password" class="form-control" id="password">
2210+        <input type="password" class="form-control" name="password" id="password">
2211       </div>
2212       <div class="form-group m-3">
2213         <button type="submit" class="btn btn-primary">Login</button>
2214       </div>
2215     </form>
2216   </div>
2217+  `)
2218+//line templates/login.qtpl:30
2219+	if p.ErrorMessage != "" {
2220+//line templates/login.qtpl:30
2221+		qw422016.N().S(`
2222+  <div class="col-md-6 offset-md-3">
2223+    <div class="alert alert-warning text-center" >
2224+        `)
2225+//line templates/login.qtpl:33
2226+		qw422016.E().S(p.ErrorMessage)
2227+//line templates/login.qtpl:33
2228+		qw422016.N().S(`
2229+    </div>
2230+  </div>
2231+  `)
2232+//line templates/login.qtpl:36
2233+	}
2234+//line templates/login.qtpl:36
2235+	qw422016.N().S(`
2236 </div>
2237 `)
2238-//line templates/login.qtpl:28
2239+//line templates/login.qtpl:38
2240 }
2241 
2242-//line templates/login.qtpl:28
2243-func (p *LoginPage) WriteContent(qq422016 qtio422016.Writer) {
2244-//line templates/login.qtpl:28
2245+//line templates/login.qtpl:38
2246+func (p *LoginPage) WriteContent(qq422016 qtio422016.Writer, ctx context.Context) {
2247+//line templates/login.qtpl:38
2248 	qw422016 := qt422016.AcquireWriter(qq422016)
2249-//line templates/login.qtpl:28
2250-	p.StreamContent(qw422016)
2251-//line templates/login.qtpl:28
2252+//line templates/login.qtpl:38
2253+	p.StreamContent(qw422016, ctx)
2254+//line templates/login.qtpl:38
2255 	qt422016.ReleaseWriter(qw422016)
2256-//line templates/login.qtpl:28
2257+//line templates/login.qtpl:38
2258 }
2259 
2260-//line templates/login.qtpl:28
2261-func (p *LoginPage) Content() string {
2262-//line templates/login.qtpl:28
2263+//line templates/login.qtpl:38
2264+func (p *LoginPage) Content(ctx context.Context) string {
2265+//line templates/login.qtpl:38
2266 	qb422016 := qt422016.AcquireByteBuffer()
2267-//line templates/login.qtpl:28
2268-	p.WriteContent(qb422016)
2269-//line templates/login.qtpl:28
2270+//line templates/login.qtpl:38
2271+	p.WriteContent(qb422016, ctx)
2272+//line templates/login.qtpl:38
2273 	qs422016 := string(qb422016.B)
2274-//line templates/login.qtpl:28
2275+//line templates/login.qtpl:38
2276 	qt422016.ReleaseByteBuffer(qb422016)
2277-//line templates/login.qtpl:28
2278+//line templates/login.qtpl:38
2279 	return qs422016
2280-//line templates/login.qtpl:28
2281+//line templates/login.qtpl:38
2282 }
2283 
2284-//line templates/login.qtpl:30
2285-func (p *LoginPage) StreamScript(qw422016 *qt422016.Writer) {
2286-//line templates/login.qtpl:30
2287+//line templates/login.qtpl:40
2288+func (p *LoginPage) StreamScript(qw422016 *qt422016.Writer, ctx context.Context) {
2289+//line templates/login.qtpl:40
2290 	qw422016.N().S(`
2291 `)
2292-//line templates/login.qtpl:31
2293+//line templates/login.qtpl:41
2294 }
2295 
2296-//line templates/login.qtpl:31
2297-func (p *LoginPage) WriteScript(qq422016 qtio422016.Writer) {
2298-//line templates/login.qtpl:31
2299+//line templates/login.qtpl:41
2300+func (p *LoginPage) WriteScript(qq422016 qtio422016.Writer, ctx context.Context) {
2301+//line templates/login.qtpl:41
2302 	qw422016 := qt422016.AcquireWriter(qq422016)
2303-//line templates/login.qtpl:31
2304-	p.StreamScript(qw422016)
2305-//line templates/login.qtpl:31
2306+//line templates/login.qtpl:41
2307+	p.StreamScript(qw422016, ctx)
2308+//line templates/login.qtpl:41
2309 	qt422016.ReleaseWriter(qw422016)
2310-//line templates/login.qtpl:31
2311+//line templates/login.qtpl:41
2312 }
2313 
2314-//line templates/login.qtpl:31
2315-func (p *LoginPage) Script() string {
2316-//line templates/login.qtpl:31
2317+//line templates/login.qtpl:41
2318+func (p *LoginPage) Script(ctx context.Context) string {
2319+//line templates/login.qtpl:41
2320 	qb422016 := qt422016.AcquireByteBuffer()
2321-//line templates/login.qtpl:31
2322-	p.WriteScript(qb422016)
2323-//line templates/login.qtpl:31
2324+//line templates/login.qtpl:41
2325+	p.WriteScript(qb422016, ctx)
2326+//line templates/login.qtpl:41
2327 	qs422016 := string(qb422016.B)
2328-//line templates/login.qtpl:31
2329+//line templates/login.qtpl:41
2330 	qt422016.ReleaseByteBuffer(qb422016)
2331-//line templates/login.qtpl:31
2332+//line templates/login.qtpl:41
2333 	return qs422016
2334-//line templates/login.qtpl:31
2335+//line templates/login.qtpl:41
2336 }
2337diff --git a/templates/navbar.qtpl b/templates/navbar.qtpl
2338index 18400b19faa99a904eecd35b103dce288b378cfb..3cd1b39dd764a9754891be72beb340c74bcae476 100644
2339--- a/templates/navbar.qtpl
2340+++ b/templates/navbar.qtpl
2341@@ -1,3 +1,5 @@
2342+{% import "context" %}
2343+
2344 {% code
2345 type Selection int
2346 const (
2347@@ -22,13 +24,17 @@ %}
2348 
2349 {% func insertIfEqual(s, d any) %}{% if s == d %} selected{% endif %}{% endfunc %}
2350 
2351-{% func Navbar (s Selection) %}
2352+{% func Navbar (ctx context.Context, s Selection) %}
2353         <nav class="container navbar navbar-expand">
2354           <div class="navbar-nav">
2355             <a class="nav-link{%= insertIfEqual(s, Git) %}" href="/">git</a>
2356           </div>
2357           <div class="navbar-nav ms-auto">
2358+          {% if IsLoggedIn(ctx) %}
2359+            <a class="nav-link{%= insertIfEqual(s, Login) %}" href="/logout">logout</a>
2360+          {% else %}
2361             <a class="nav-link{%= insertIfEqual(s, Login) %}" href="/login">login</a>
2362+          {% endif %}
2363 {% comment %}
2364 Add this back once needed
2365             <a class="nav-link{%= insertIfEqual(s, List) %}" href="/list/">list</a>
2366diff --git a/templates/navbar.qtpl.go b/templates/navbar.qtpl.go
2367index e080785da7c19c0dee4de95c1e329e319d293957..d900c9cfe6bf59b13261796d539952e453cec6d0 100644
2368--- a/templates/navbar.qtpl.go
2369+++ b/templates/navbar.qtpl.go
2370@@ -5,19 +5,22 @@ //line templates/navbar.qtpl:1
2371 package templates
2372 
2373 //line templates/navbar.qtpl:1
2374+import "context"
2375+
2376+//line templates/navbar.qtpl:3
2377 import (
2378 	qtio422016 "io"
2379 
2380 	qt422016 "github.com/valyala/quicktemplate"
2381 )
2382 
2383-//line templates/navbar.qtpl:1
2384+//line templates/navbar.qtpl:3
2385 var (
2386 	_ = qtio422016.Copy
2387 	_ = qt422016.AcquireByteBuffer
2388 )
2389 
2390-//line templates/navbar.qtpl:2
2391+//line templates/navbar.qtpl:4
2392 type Selection int
2393 
2394 const (
2395@@ -28,7 +31,7 @@ 	Config
2396 	Login
2397 )
2398 
2399-//line templates/navbar.qtpl:13
2400+//line templates/navbar.qtpl:15
2401 type GitSelection int
2402 
2403 const (
2404@@ -39,213 +42,233 @@ 	Refs
2405 	Tree
2406 )
2407 
2408-//line templates/navbar.qtpl:23
2409+//line templates/navbar.qtpl:25
2410 func streaminsertIfEqual(qw422016 *qt422016.Writer, s, d any) {
2411-//line templates/navbar.qtpl:23
2412+//line templates/navbar.qtpl:25
2413 	if s == d {
2414-//line templates/navbar.qtpl:23
2415+//line templates/navbar.qtpl:25
2416 		qw422016.N().S(` selected`)
2417-//line templates/navbar.qtpl:23
2418+//line templates/navbar.qtpl:25
2419 	}
2420-//line templates/navbar.qtpl:23
2421+//line templates/navbar.qtpl:25
2422 }
2423 
2424-//line templates/navbar.qtpl:23
2425+//line templates/navbar.qtpl:25
2426 func writeinsertIfEqual(qq422016 qtio422016.Writer, s, d any) {
2427-//line templates/navbar.qtpl:23
2428+//line templates/navbar.qtpl:25
2429 	qw422016 := qt422016.AcquireWriter(qq422016)
2430-//line templates/navbar.qtpl:23
2431+//line templates/navbar.qtpl:25
2432 	streaminsertIfEqual(qw422016, s, d)
2433-//line templates/navbar.qtpl:23
2434+//line templates/navbar.qtpl:25
2435 	qt422016.ReleaseWriter(qw422016)
2436-//line templates/navbar.qtpl:23
2437+//line templates/navbar.qtpl:25
2438 }
2439 
2440-//line templates/navbar.qtpl:23
2441+//line templates/navbar.qtpl:25
2442 func insertIfEqual(s, d any) string {
2443-//line templates/navbar.qtpl:23
2444+//line templates/navbar.qtpl:25
2445 	qb422016 := qt422016.AcquireByteBuffer()
2446-//line templates/navbar.qtpl:23
2447+//line templates/navbar.qtpl:25
2448 	writeinsertIfEqual(qb422016, s, d)
2449-//line templates/navbar.qtpl:23
2450+//line templates/navbar.qtpl:25
2451 	qs422016 := string(qb422016.B)
2452-//line templates/navbar.qtpl:23
2453+//line templates/navbar.qtpl:25
2454 	qt422016.ReleaseByteBuffer(qb422016)
2455-//line templates/navbar.qtpl:23
2456+//line templates/navbar.qtpl:25
2457 	return qs422016
2458-//line templates/navbar.qtpl:23
2459+//line templates/navbar.qtpl:25
2460 }
2461 
2462-//line templates/navbar.qtpl:25
2463-func StreamNavbar(qw422016 *qt422016.Writer, s Selection) {
2464-//line templates/navbar.qtpl:25
2465+//line templates/navbar.qtpl:27
2466+func StreamNavbar(qw422016 *qt422016.Writer, ctx context.Context, s Selection) {
2467+//line templates/navbar.qtpl:27
2468 	qw422016.N().S(`
2469         <nav class="container navbar navbar-expand">
2470           <div class="navbar-nav">
2471             <a class="nav-link`)
2472-//line templates/navbar.qtpl:28
2473+//line templates/navbar.qtpl:30
2474 	streaminsertIfEqual(qw422016, s, Git)
2475-//line templates/navbar.qtpl:28
2476+//line templates/navbar.qtpl:30
2477 	qw422016.N().S(`" href="/">git</a>
2478           </div>
2479           <div class="navbar-nav ms-auto">
2480+          `)
2481+//line templates/navbar.qtpl:33
2482+	if IsLoggedIn(ctx) {
2483+//line templates/navbar.qtpl:33
2484+		qw422016.N().S(`
2485             <a class="nav-link`)
2486-//line templates/navbar.qtpl:31
2487-	streaminsertIfEqual(qw422016, s, Login)
2488-//line templates/navbar.qtpl:31
2489-	qw422016.N().S(`" href="/login">login</a>
2490+//line templates/navbar.qtpl:34
2491+		streaminsertIfEqual(qw422016, s, Login)
2492+//line templates/navbar.qtpl:34
2493+		qw422016.N().S(`" href="/logout">logout</a>
2494+          `)
2495+//line templates/navbar.qtpl:35
2496+	} else {
2497+//line templates/navbar.qtpl:35
2498+		qw422016.N().S(`
2499+            <a class="nav-link`)
2500+//line templates/navbar.qtpl:36
2501+		streaminsertIfEqual(qw422016, s, Login)
2502+//line templates/navbar.qtpl:36
2503+		qw422016.N().S(`" href="/login">login</a>
2504+          `)
2505+//line templates/navbar.qtpl:37
2506+	}
2507+//line templates/navbar.qtpl:37
2508+	qw422016.N().S(`
2509 `)
2510-//line templates/navbar.qtpl:35
2511+//line templates/navbar.qtpl:41
2512 	qw422016.N().S(`
2513 `)
2514-//line templates/navbar.qtpl:39
2515+//line templates/navbar.qtpl:45
2516 	qw422016.N().S(`
2517           </div>
2518         </nav>
2519 `)
2520-//line templates/navbar.qtpl:42
2521+//line templates/navbar.qtpl:48
2522 }
2523 
2524-//line templates/navbar.qtpl:42
2525-func WriteNavbar(qq422016 qtio422016.Writer, s Selection) {
2526-//line templates/navbar.qtpl:42
2527+//line templates/navbar.qtpl:48
2528+func WriteNavbar(qq422016 qtio422016.Writer, ctx context.Context, s Selection) {
2529+//line templates/navbar.qtpl:48
2530 	qw422016 := qt422016.AcquireWriter(qq422016)
2531-//line templates/navbar.qtpl:42
2532-	StreamNavbar(qw422016, s)
2533-//line templates/navbar.qtpl:42
2534+//line templates/navbar.qtpl:48
2535+	StreamNavbar(qw422016, ctx, s)
2536+//line templates/navbar.qtpl:48
2537 	qt422016.ReleaseWriter(qw422016)
2538-//line templates/navbar.qtpl:42
2539+//line templates/navbar.qtpl:48
2540 }
2541 
2542-//line templates/navbar.qtpl:42
2543-func Navbar(s Selection) string {
2544-//line templates/navbar.qtpl:42
2545+//line templates/navbar.qtpl:48
2546+func Navbar(ctx context.Context, s Selection) string {
2547+//line templates/navbar.qtpl:48
2548 	qb422016 := qt422016.AcquireByteBuffer()
2549-//line templates/navbar.qtpl:42
2550-	WriteNavbar(qb422016, s)
2551-//line templates/navbar.qtpl:42
2552+//line templates/navbar.qtpl:48
2553+	WriteNavbar(qb422016, ctx, s)
2554+//line templates/navbar.qtpl:48
2555 	qs422016 := string(qb422016.B)
2556-//line templates/navbar.qtpl:42
2557+//line templates/navbar.qtpl:48
2558 	qt422016.ReleaseByteBuffer(qb422016)
2559-//line templates/navbar.qtpl:42
2560+//line templates/navbar.qtpl:48
2561 	return qs422016
2562-//line templates/navbar.qtpl:42
2563+//line templates/navbar.qtpl:48
2564 }
2565 
2566-//line templates/navbar.qtpl:44
2567+//line templates/navbar.qtpl:50
2568 func StreamGitItemNav(qw422016 *qt422016.Writer, name, ref string, s GitSelection) {
2569-//line templates/navbar.qtpl:44
2570+//line templates/navbar.qtpl:50
2571 	qw422016.N().S(`
2572 <div class="row">
2573     <h3 id="name">`)
2574-//line templates/navbar.qtpl:46
2575+//line templates/navbar.qtpl:52
2576 	qw422016.E().S(name)
2577-//line templates/navbar.qtpl:46
2578+//line templates/navbar.qtpl:52
2579 	qw422016.N().S(` `)
2580-//line templates/navbar.qtpl:46
2581+//line templates/navbar.qtpl:52
2582 	if ref != "" && (s == Log || s == Tree) {
2583-//line templates/navbar.qtpl:46
2584+//line templates/navbar.qtpl:52
2585 		qw422016.N().S(`@ `)
2586-//line templates/navbar.qtpl:46
2587+//line templates/navbar.qtpl:52
2588 		qw422016.E().S(ref)
2589-//line templates/navbar.qtpl:46
2590+//line templates/navbar.qtpl:52
2591 	}
2592-//line templates/navbar.qtpl:46
2593+//line templates/navbar.qtpl:52
2594 	qw422016.N().S(`</h3>
2595 </div>
2596 <div class="row">
2597   <ul class="nav">
2598     <li class="nav-item">
2599       <a class="nav-link`)
2600-//line templates/navbar.qtpl:51
2601+//line templates/navbar.qtpl:57
2602 	streaminsertIfEqual(qw422016, s, Readme)
2603-//line templates/navbar.qtpl:51
2604+//line templates/navbar.qtpl:57
2605 	qw422016.N().S(`" aria-current="page" href="/`)
2606-//line templates/navbar.qtpl:51
2607+//line templates/navbar.qtpl:57
2608 	qw422016.E().S(name)
2609-//line templates/navbar.qtpl:51
2610+//line templates/navbar.qtpl:57
2611 	qw422016.N().S(`/about/">about</a>
2612     </li>
2613     <li class="nav-item">
2614       <a class="nav-link`)
2615-//line templates/navbar.qtpl:54
2616+//line templates/navbar.qtpl:60
2617 	streaminsertIfEqual(qw422016, s, Summary)
2618-//line templates/navbar.qtpl:54
2619+//line templates/navbar.qtpl:60
2620 	qw422016.N().S(`" aria-current="page" href="/`)
2621-//line templates/navbar.qtpl:54
2622+//line templates/navbar.qtpl:60
2623 	qw422016.E().S(name)
2624-//line templates/navbar.qtpl:54
2625+//line templates/navbar.qtpl:60
2626 	qw422016.N().S(`/">summary</a>
2627     </li>
2628     <li class="nav-item">
2629       <a class="nav-link`)
2630-//line templates/navbar.qtpl:57
2631+//line templates/navbar.qtpl:63
2632 	streaminsertIfEqual(qw422016, s, Refs)
2633-//line templates/navbar.qtpl:57
2634+//line templates/navbar.qtpl:63
2635 	qw422016.N().S(`" aria-current="page" href="/`)
2636-//line templates/navbar.qtpl:57
2637+//line templates/navbar.qtpl:63
2638 	qw422016.E().S(name)
2639-//line templates/navbar.qtpl:57
2640+//line templates/navbar.qtpl:63
2641 	qw422016.N().S(`/refs/">refs</a>
2642     </li>
2643     <li class="nav-item">
2644       <a class="nav-link`)
2645-//line templates/navbar.qtpl:60
2646+//line templates/navbar.qtpl:66
2647 	streaminsertIfEqual(qw422016, s, Log)
2648-//line templates/navbar.qtpl:60
2649+//line templates/navbar.qtpl:66
2650 	qw422016.N().S(`" aria-current="page" href="/`)
2651-//line templates/navbar.qtpl:60
2652+//line templates/navbar.qtpl:66
2653 	qw422016.E().S(name)
2654-//line templates/navbar.qtpl:60
2655+//line templates/navbar.qtpl:66
2656 	qw422016.N().S(`/log/`)
2657-//line templates/navbar.qtpl:60
2658+//line templates/navbar.qtpl:66
2659 	qw422016.E().S(ref)
2660-//line templates/navbar.qtpl:60
2661+//line templates/navbar.qtpl:66
2662 	qw422016.N().S(`/">log</a>
2663     </li>
2664     <li class="nav-item">
2665       <a class="nav-link`)
2666-//line templates/navbar.qtpl:63
2667+//line templates/navbar.qtpl:69
2668 	streaminsertIfEqual(qw422016, s, Tree)
2669-//line templates/navbar.qtpl:63
2670+//line templates/navbar.qtpl:69
2671 	qw422016.N().S(`" aria-current="page" href="/`)
2672-//line templates/navbar.qtpl:63
2673+//line templates/navbar.qtpl:69
2674 	qw422016.E().S(name)
2675-//line templates/navbar.qtpl:63
2676+//line templates/navbar.qtpl:69
2677 	qw422016.N().S(`/tree/`)
2678-//line templates/navbar.qtpl:63
2679+//line templates/navbar.qtpl:69
2680 	qw422016.E().S(ref)
2681-//line templates/navbar.qtpl:63
2682+//line templates/navbar.qtpl:69
2683 	qw422016.N().S(`/">tree</a>
2684     </li>
2685   </ul>
2686 </div>
2687 `)
2688-//line templates/navbar.qtpl:67
2689+//line templates/navbar.qtpl:73
2690 }
2691 
2692-//line templates/navbar.qtpl:67
2693+//line templates/navbar.qtpl:73
2694 func WriteGitItemNav(qq422016 qtio422016.Writer, name, ref string, s GitSelection) {
2695-//line templates/navbar.qtpl:67
2696+//line templates/navbar.qtpl:73
2697 	qw422016 := qt422016.AcquireWriter(qq422016)
2698-//line templates/navbar.qtpl:67
2699+//line templates/navbar.qtpl:73
2700 	StreamGitItemNav(qw422016, name, ref, s)
2701-//line templates/navbar.qtpl:67
2702+//line templates/navbar.qtpl:73
2703 	qt422016.ReleaseWriter(qw422016)
2704-//line templates/navbar.qtpl:67
2705+//line templates/navbar.qtpl:73
2706 }
2707 
2708-//line templates/navbar.qtpl:67
2709+//line templates/navbar.qtpl:73
2710 func GitItemNav(name, ref string, s GitSelection) string {
2711-//line templates/navbar.qtpl:67
2712+//line templates/navbar.qtpl:73
2713 	qb422016 := qt422016.AcquireByteBuffer()
2714-//line templates/navbar.qtpl:67
2715+//line templates/navbar.qtpl:73
2716 	WriteGitItemNav(qb422016, name, ref, s)
2717-//line templates/navbar.qtpl:67
2718+//line templates/navbar.qtpl:73
2719 	qs422016 := string(qb422016.B)
2720-//line templates/navbar.qtpl:67
2721+//line templates/navbar.qtpl:73
2722 	qt422016.ReleaseByteBuffer(qb422016)
2723-//line templates/navbar.qtpl:67
2724+//line templates/navbar.qtpl:73
2725 	return qs422016
2726-//line templates/navbar.qtpl:67
2727+//line templates/navbar.qtpl:73
2728 }