core/ui: fix page title (#4957)

* core/ui: fix page title

* cache template
This commit is contained in:
Caleb Doxsey 2024-02-12 14:05:18 -07:00 committed by GitHub
parent 76862c2fe8
commit c6d1f17100
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 56 additions and 31 deletions

View file

@ -10,6 +10,6 @@ import (
// DeviceEnrolled displays an HTML page informing the user that they've successfully enrolled a device. // DeviceEnrolled displays an HTML page informing the user that they've successfully enrolled a device.
func DeviceEnrolled(data UserInfoData) http.Handler { func DeviceEnrolled(data UserInfoData) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { 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())
}) })
} }

View file

@ -24,6 +24,6 @@ func SignedOut(data SignedOutData) http.Handler {
} }
// otherwise show the signed-out page // otherwise show the signed-out page
return ui.ServePage(w, r, "SignedOut", data.ToJSON()) return ui.ServePage(w, r, "SignedOut", "Signed Out", data.ToJSON())
}) })
} }

View file

@ -22,6 +22,6 @@ func (data SignOutConfirmData) ToJSON() map[string]interface{} {
// SignOutConfirm returns a handler that renders the sign out confirm page. // SignOutConfirm returns a handler that renders the sign out confirm page.
func SignOutConfirm(data SignOutConfirmData) http.Handler { func SignOutConfirm(data SignOutConfirmData) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { 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())
}) })
} }

View file

@ -65,6 +65,6 @@ func (data UserInfoData) ToJSON() map[string]any {
// UserInfo returns a handler that renders the user info page. // UserInfo returns a handler that renders the user info page.
func UserInfo(data UserInfoData) http.Handler { func UserInfo(data UserInfoData) http.Handler {
return httputil.HandlerFunc(func(w http.ResponseWriter, r *http.Request) error { 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())
}) })
} }

View file

@ -402,7 +402,7 @@ func (h *Handler) handleView(w http.ResponseWriter, r *http.Request, state *Stat
"selfUrl": r.URL.String(), "selfUrl": r.URL.String(),
} }
httputil.AddBrandingOptionsToMap(m, state.BrandingOptions) 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 { func (h *Handler) saveSessionAndRedirect(w http.ResponseWriter, r *http.Request, state *State, rawRedirectURI string) error {

View file

@ -2,6 +2,7 @@ package httputil
import ( import (
"context" "context"
"fmt"
"net/http" "net/http"
"net/url" "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.Header().Set("Content-Type", "text/html; charset=UTF-8")
w.WriteHeader(response.Status) 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) http.Error(w, err.Error(), http.StatusInternalServerError)
} }
} }

View file

@ -25,14 +25,14 @@
name="viewport" name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no" content="width=device-width, initial-scale=1, shrink-to-fit=no"
/> />
<title>User info dashboard</title> <title>{{.Title}}</title>
<link rel="stylesheet" href="/.pomerium/index.css" /> <link rel="stylesheet" href="/.pomerium/index.css" />
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root"></div>
<script> <script>
window.POMERIUM_DATA = {}; window.POMERIUM_DATA = {{.Data}};
</script> </script>
<script src="/.pomerium/index.js"></script> <script src="/.pomerium/index.js"></script>
</body> </body>

View file

@ -3,10 +3,12 @@ package ui
import ( import (
"bytes" "bytes"
"encoding/json" "html/template"
"io" "io"
"io/fs"
"net/http" "net/http"
"path/filepath" "path/filepath"
"sync"
"time" "time"
"github.com/pomerium/csrf" "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. // 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 { if data == nil {
data = make(map[string]interface{}) data = make(map[string]any)
} }
data["csrfToken"] = csrf.Token(r) data["csrfToken"] = csrf.Token(r)
data["page"] = page data["page"] = page
jsonData, err := json.Marshal(data) bs, err := renderIndex(map[string]any{
"Title": title,
"Data": data,
})
if err != nil { if err != nil {
return err 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)) http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(bs))
return nil return nil
} }
var startTime = time.Now() 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
}

View file

@ -7,10 +7,8 @@ import (
"io/fs" "io/fs"
) )
var ( // ExtUIFS must be set to provide access to UI dist/ files
// ExtUIFS must be set to provide access to UI dist/ files var ExtUIFS fs.FS
ExtUIFS fs.FS
)
func openFile(name string) (f fs.File, etag string, err error) { func openFile(name string) (f fs.File, etag string, err error) {
if ExtUIFS == nil { if ExtUIFS == nil {