From c6d1f171006bfb1c3e260061d02eb3ce10977df4 Mon Sep 17 00:00:00 2001 From: Caleb Doxsey Date: Mon, 12 Feb 2024 14:05:18 -0700 Subject: [PATCH] core/ui: fix page title (#4957) * core/ui: fix page title * cache template --- internal/handlers/device-enrolled.go | 2 +- internal/handlers/signedout.go | 2 +- internal/handlers/signout.go | 2 +- internal/handlers/userinfo.go | 2 +- internal/handlers/webauthn/webauthn.go | 2 +- internal/httputil/errors.go | 3 +- ui/dist/{index.html => index.gohtml} | 4 +- ui/embed.go | 64 ++++++++++++++++++-------- ui/embed_ext.go | 6 +-- 9 files changed, 56 insertions(+), 31 deletions(-) rename ui/dist/{index.html => index.gohtml} (92%) diff --git a/internal/handlers/device-enrolled.go b/internal/handlers/device-enrolled.go index d8740dd49..e0fbf4b97 100644 --- a/internal/handlers/device-enrolled.go +++ b/internal/handlers/device-enrolled.go @@ -10,6 +10,6 @@ import ( // DeviceEnrolled displays an HTML page informing the user that they've successfully enrolled a device. func DeviceEnrolled(data UserInfoData) http.Handler { return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - return ui.ServePage(w, r, "DeviceEnrolled", data.ToJSON()) + return ui.ServePage(w, r, "DeviceEnrolled", "Device Enrolled", data.ToJSON()) }) } diff --git a/internal/handlers/signedout.go b/internal/handlers/signedout.go index 5fd0440ca..0914220e8 100644 --- a/internal/handlers/signedout.go +++ b/internal/handlers/signedout.go @@ -24,6 +24,6 @@ func SignedOut(data SignedOutData) http.Handler { } // otherwise show the signed-out page - return ui.ServePage(w, r, "SignedOut", data.ToJSON()) + return ui.ServePage(w, r, "SignedOut", "Signed Out", data.ToJSON()) }) } diff --git a/internal/handlers/signout.go b/internal/handlers/signout.go index db2b0aac3..dfe37735b 100644 --- a/internal/handlers/signout.go +++ b/internal/handlers/signout.go @@ -22,6 +22,6 @@ func (data SignOutConfirmData) ToJSON() map[string]interface{} { // SignOutConfirm returns a handler that renders the sign out confirm page. func SignOutConfirm(data SignOutConfirmData) http.Handler { return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - return ui.ServePage(w, r, "SignOutConfirm", data.ToJSON()) + return ui.ServePage(w, r, "SignOutConfirm", "Confirm Sign Out", data.ToJSON()) }) } diff --git a/internal/handlers/userinfo.go b/internal/handlers/userinfo.go index ea3737d1a..c430d4f85 100644 --- a/internal/handlers/userinfo.go +++ b/internal/handlers/userinfo.go @@ -65,6 +65,6 @@ func (data UserInfoData) ToJSON() map[string]any { // UserInfo returns a handler that renders the user info page. func UserInfo(data UserInfoData) http.Handler { return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { - return ui.ServePage(w, r, "UserInfo", data.ToJSON()) + return ui.ServePage(w, r, "UserInfo", "User Info Dashboard", data.ToJSON()) }) } diff --git a/internal/handlers/webauthn/webauthn.go b/internal/handlers/webauthn/webauthn.go index 45e0f95a5..468556c2f 100644 --- a/internal/handlers/webauthn/webauthn.go +++ b/internal/handlers/webauthn/webauthn.go @@ -402,7 +402,7 @@ func (h *Handler) handleView(w http.ResponseWriter, r *http.Request, state *Stat "selfUrl": r.URL.String(), } httputil.AddBrandingOptionsToMap(m, state.BrandingOptions) - return ui.ServePage(w, r, "WebAuthnRegistration", m) + return ui.ServePage(w, r, "WebAuthnRegistration", "Device Registration", m) } func (h *Handler) saveSessionAndRedirect(w http.ResponseWriter, r *http.Request, state *State, rawRedirectURI string) error { diff --git a/internal/httputil/errors.go b/internal/httputil/errors.go index 35e6d01d9..e38b1ea05 100644 --- a/internal/httputil/errors.go +++ b/internal/httputil/errors.go @@ -2,6 +2,7 @@ package httputil import ( "context" + "fmt" "net/http" "net/url" @@ -101,7 +102,7 @@ func (e *HTTPError) ErrorResponse(ctx context.Context, w http.ResponseWriter, r w.Header().Set("Content-Type", "text/html; charset=UTF-8") w.WriteHeader(response.Status) - if err := ui.ServePage(w, r, "Error", m); err != nil { + if err := ui.ServePage(w, r, "Error", fmt.Sprintf("%d %s", response.Status, response.StatusText), m); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } diff --git a/ui/dist/index.html b/ui/dist/index.gohtml similarity index 92% rename from ui/dist/index.html rename to ui/dist/index.gohtml index 16fa094c6..ffbefdf10 100644 --- a/ui/dist/index.html +++ b/ui/dist/index.gohtml @@ -25,14 +25,14 @@ name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - User info dashboard + {{.Title}}
diff --git a/ui/embed.go b/ui/embed.go index d0fc0e9f4..e765f41f2 100644 --- a/ui/embed.go +++ b/ui/embed.go @@ -3,10 +3,12 @@ package ui import ( "bytes" - "encoding/json" + "html/template" "io" + "io/fs" "net/http" "path/filepath" + "sync" "time" "github.com/pomerium/csrf" @@ -26,35 +28,59 @@ func ServeFile(w http.ResponseWriter, r *http.Request, filePath string) error { } // ServePage serves the index.html page. -func ServePage(w http.ResponseWriter, r *http.Request, page string, data map[string]interface{}) error { +func ServePage(w http.ResponseWriter, r *http.Request, page, title string, data map[string]interface{}) error { if data == nil { - data = make(map[string]interface{}) + data = make(map[string]any) } data["csrfToken"] = csrf.Token(r) data["page"] = page - jsonData, err := json.Marshal(data) + bs, err := renderIndex(map[string]any{ + "Title": title, + "Data": data, + }) if err != nil { return err } - f, _, err := openFile("dist/index.html") - if err != nil { - return err - } - bs, err := io.ReadAll(f) - _ = f.Close() - if err != nil { - return err - } - - bs = bytes.Replace(bs, - []byte("window.POMERIUM_DATA = {}"), - append([]byte("window.POMERIUM_DATA = "), jsonData...), - 1) - http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(bs)) return nil } var startTime = time.Now() + +func renderIndex(data any) ([]byte, error) { + tpl, err := parseIndex() + if err != nil { + return nil, err + } + + var buf bytes.Buffer + err = tpl.Execute(&buf, data) + return buf.Bytes(), err +} + +var ( + parseIndexOnce sync.Once + parseIndexTemplate *template.Template + parseIndexError error +) + +func parseIndex() (*template.Template, error) { + parseIndexOnce.Do(func() { + var f fs.File + f, _, parseIndexError = openFile("dist/index.gohtml") + if parseIndexError != nil { + return + } + var bs []byte + bs, parseIndexError = io.ReadAll(f) + _ = f.Close() + if parseIndexError != nil { + return + } + + parseIndexTemplate, parseIndexError = template.New("").Parse(string(bs)) + }) + return parseIndexTemplate, parseIndexError +} diff --git a/ui/embed_ext.go b/ui/embed_ext.go index caf61c66f..da52a883b 100644 --- a/ui/embed_ext.go +++ b/ui/embed_ext.go @@ -7,10 +7,8 @@ import ( "io/fs" ) -var ( - // ExtUIFS must be set to provide access to UI dist/ files - ExtUIFS fs.FS -) +// ExtUIFS must be set to provide access to UI dist/ files +var ExtUIFS fs.FS func openFile(name string) (f fs.File, etag string, err error) { if ExtUIFS == nil {