Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(107)

Side by Side Diff: milo/appengine/settings/themes.go

Issue 2748073006: Milo Refactor: Remove theme support (Closed)
Patch Set: Fix builder.html pointer Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 package settings
6
7 import (
8 "fmt"
9 "io"
10 "net/http"
11 "path"
12 "sort"
13 "strings"
14
15 "github.com/julienschmidt/httprouter"
16 "github.com/luci/gae/service/info"
17 "github.com/luci/luci-go/appengine/gaeauth/server"
18 "github.com/luci/luci-go/appengine/gaemiddleware"
19 "github.com/luci/luci-go/common/clock"
20 "github.com/luci/luci-go/milo/common/miloerror"
21 "github.com/luci/luci-go/server/analytics"
22 "github.com/luci/luci-go/server/auth"
23 "github.com/luci/luci-go/server/router"
24 "github.com/luci/luci-go/server/templates"
25 "golang.org/x/net/context"
26 )
27
28 type themeContextKey string
29
30 // NamedBundle is a tuple of a name (That matches it's corresponding theme)
31 // and a template bundle.
32 type NamedBundle struct {
33 Name string
34 Bundle *templates.Bundle
35 Theme *Theme
36 }
37
38 // Theme is the base type for specifying where to find a Theme.
39 type Theme struct {
40 // IsTemplate is true if this theme is a Go template type template, and false
41 // if it is a client side (eg. Polymer) type template.
42 IsTemplate bool
43 // Name is the name of the Theme.
44 Name string
45 }
46
47 // ThemedHandler is the base type for any milo html handlers.
48 type ThemedHandler interface {
49 // Return the template name given a theme name.
50 GetTemplateName(Theme) string
51 // Render the page for server side renders.
52 Render(context.Context, *http.Request, httprouter.Params) (*templates.Ar gs, error)
53 }
54
55 var (
56 // Default is the global default theme for anonomyous users.
57 Default = Theme{IsTemplate: true, Name: "buildbot"}
58 // Themes is a list of all known themes.
59 Themes = map[string]Theme{
60 "buildbot": Default,
61 "bootstrap": {IsTemplate: true, Name: "bootstrap"},
62 }
63 )
64
65 // GetAllThemes gets all known themes as a list of strings.
66 func GetAllThemes() []string {
67 results := make([]string, 0, len(Themes))
68 for k := range Themes {
69 results = append(results, k)
70 }
71 sort.Strings(results)
72 return results
73 }
74
75 // GetTemplateBundles is used to render HTML templates. It provides a base args
76 // passed to all templates.
77 func GetTemplateBundles() []NamedBundle {
78 result := []NamedBundle{}
79 for name, t := range Themes {
80 if t.IsTemplate {
81 templateBundle := &templates.Bundle{
82 Loader: templates.FileSystemLoader(path .Join("templates", name)),
83 DefaultTemplate: name,
84 DebugMode: info.IsDevAppServer,
85 DefaultArgs: func(c context.Context) (templates. Args, error) {
86 r := getRequest(c)
87 path := r.URL.Path
88 loginURL, err := auth.LoginURL(c, path)
89 if err != nil {
90 return nil, err
91 }
92 logoutURL, err := auth.LogoutURL(c, path )
93 if err != nil {
94 return nil, err
95 }
96 if err != nil {
97 return nil, err
98 }
99 return templates.Args{
100 "AppVersion": strings.Split(inf o.VersionID(c), ".")[0],
101 "IsAnonymous": auth.CurrentIdent ity(c) == "anonymous:anonymous",
102 "User": auth.CurrentUser( c),
103 "LoginURL": loginURL,
104 "LogoutURL": logoutURL,
105 "CurrentTime": clock.Now(c),
106 "Analytics": analytics.Snippet (c),
107 "RequestID": info.RequestID(c) ,
108 }, nil
109 },
110 FuncMap: funcMap,
111 }
112 result = append(result, NamedBundle{name, templateBundle , &t})
113 }
114 }
115 return result
116 }
117
118 // UseNamedBundle is like templates.Use, but with the choice of one of many bund les (themes)
119 func UseNamedBundle(c context.Context, nb NamedBundle) (context.Context, error) {
120 err := nb.Bundle.EnsureLoaded(c)
121 return context.WithValue(c, themeContextKey(nb.Name), nb.Bundle), err
122 }
123
124 // withNamedBundle is like templates.WithTemplates, but with the choice of one o f many bundles (themes)
125 func withNamedBundle(nb NamedBundle) router.Middleware {
126 return func(c *router.Context, next router.Handler) {
127 var err error
128 c.Context, err = UseNamedBundle(c.Context, nb) // calls EnsureLo aded and initializes b.err inside
129 if err != nil {
130 http.Error(c.Writer, fmt.Sprintf("Can't load HTML templa tes.\n%s", err), http.StatusInternalServerError)
131 return
132 }
133 next(c)
134 }
135 }
136
137 // themedMustRender renders theme and panics if it can't be rendered. This shou ld never fail in
138 // production.
139 func themedMustRender(c context.Context, out io.Writer, theme, name string, args templates.Args) {
140 if b, _ := c.Value(themeContextKey(theme)).(*templates.Bundle); b != nil {
141 blob, err := b.Render(c, name, args)
142 if err != nil {
143 panic(fmt.Errorf("Could not render template %s from them e %s:\n%s", name, theme, err))
144 }
145 _, err = out.Write(blob)
146 if err != nil {
147 panic(fmt.Errorf("Could not write out template %s from t heme %s:\n%s", name, theme, err))
148 }
149 return
150 }
151 panic(fmt.Errorf("Error: Could not load template %s from theme %s", name , theme))
152 }
153
154 // Base returns the basic luci appengine middlewares.
155 func Base() router.MiddlewareChain {
156 methods := auth.Authenticator{
157 &server.OAuth2Method{Scopes: []string{server.EmailScope}},
158 server.CookieAuth,
159 &server.InboundAppIDAuthMethod{},
160 }
161 m := gaemiddleware.BaseProd().Extend(auth.Use(methods), auth.Authenticat e)
162 for _, nb := range GetTemplateBundles() {
163 m = m.Extend(withNamedBundle(nb))
164 }
165 return m
166 }
167
168 // Wrap adapts a ThemedHandler into a router.Handler. Of note, the
169 // Render functions' interface into rendering is purely through a single
170 // templates.Args value which gets rendered here, while the http.ResponseWriter
171 // is stripped out.
172 func Wrap(h ThemedHandler) router.Handler {
173 return func(c *router.Context) {
174 // Figure out if we need to do the things.
175 theme := GetTheme(c.Context, c.Request)
176 template := h.GetTemplateName(theme)
177
178 // Inject the request into the context.
179 c.Context = WithRequest(c.Context, c.Request)
180
181 // Do the things.
182 args, err := h.Render(c.Context, c.Request, c.Params)
183
184 // Throw errors.
185 // TODO(hinoka): Add themes and templates for errors so they loo k better.
186 if err != nil {
187 msg := err.Error()
188 code := http.StatusInternalServerError
189 if merr, ok := err.(miloerror.Error); ok {
190 msg = merr.Message
191 code = merr.Code
192 }
193 c.Writer.WriteHeader(code)
194 name := "pages/error.html"
195 themedMustRender(c.Context, c.Writer, theme.Name, name, templates.Args{
196 "Code": code,
197 "Message": msg,
198 })
199 return
200 }
201
202 // Render the stuff.
203 name := fmt.Sprintf("pages/%s", template)
204 themedMustRender(c.Context, c.Writer, theme.Name, name, *args)
205 }
206 }
207
208 // The context key
209 var requestKey = "http.request"
210
211 func WithRequest(c context.Context, r *http.Request) context.Context {
212 return context.WithValue(c, &requestKey, r)
213 }
214
215 func getRequest(c context.Context) *http.Request {
216 if req, ok := c.Value(&requestKey).(*http.Request); ok {
217 return req
218 }
219 panic("No http.request found in context")
220 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698