satellite/console/server: parse html files as templates (#2750)

This commit is contained in:
Yehor Butko 2019-08-13 15:37:01 +03:00 committed by GitHub
parent f3db409103
commit ce5c45b33c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 132 additions and 153 deletions

View File

@ -72,7 +72,15 @@ type Server struct {
listener net.Listener
server http.Server
schema graphql.Schema
schema graphql.Schema
templates struct {
index *template.Template
pageNotFound *template.Template
usageReport *template.Template
resetPassword *template.Template
success *template.Template
activated *template.Template
}
}
// NewServer creates new instance of console server
@ -117,8 +125,42 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, mail
return &server
}
// Run starts the server that host webapp and api endpoint
func (server *Server) Run(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)
server.schema, err = consoleql.CreateSchema(server.log, server.service, server.mailService)
if err != nil {
return Error.Wrap(err)
}
err = server.initializeTemplates()
if err != nil {
// TODO: should it return error if some template can not be initialized or just log about it?
return Error.Wrap(err)
}
ctx, cancel := context.WithCancel(ctx)
var group errgroup.Group
group.Go(func() error {
<-ctx.Done()
return server.server.Shutdown(nil)
})
group.Go(func() error {
defer cancel()
return server.server.Serve(server.listener)
})
return group.Wait()
}
// Close closes server and underlying listener
func (server *Server) Close() error {
return server.server.Close()
}
// appHandler is web app http handler function
func (server *Server) appHandler(w http.ResponseWriter, req *http.Request) {
func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
header := w.Header()
cspValues := []string{
@ -131,7 +173,11 @@ func (server *Server) appHandler(w http.ResponseWriter, req *http.Request) {
header.Set("Content-Type", "text/html; charset=UTF-8")
header.Set("Content-Security-Policy", strings.Join(cspValues, "; "))
http.ServeFile(w, req, filepath.Join(server.config.StaticDir, "dist", "index.html"))
if server.templates.index == nil || server.templates.index.Execute(w, nil) != nil {
server.log.Error("satellite/console/server: index template could not be executed")
server.serveError(w, r, http.StatusNotFound)
return
}
}
// bucketUsageReportHandler generate bucket usage report page for project
@ -147,8 +193,8 @@ func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Re
if err != nil {
server.log.Error("bucket usage report error", zap.Error(err))
w.WriteHeader(http.StatusUnauthorized)
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "errors", "404.html"))
// TODO: use http.StatusUnauthorized status when appropriate page will be created
server.serveError(w, r, http.StatusNotFound)
return
}
@ -156,8 +202,8 @@ func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Re
if err != nil {
server.log.Error("bucket usage report error", zap.Error(err))
w.WriteHeader(http.StatusUnauthorized)
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "errors", "404.html"))
//TODO: when new error pages will be created - change http.StatusNotFound on http.StatusUnauthorized
server.serveError(w, r, http.StatusNotFound)
return
}
@ -165,8 +211,8 @@ func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Re
if err != nil {
server.log.Error("bucket usage report error", zap.Error(err))
w.WriteHeader(http.StatusNotFound)
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "errors", "404.html"))
server.serveError(w, r, http.StatusNotFound)
return
}
}()
@ -198,12 +244,11 @@ func (server *Server) bucketUsageReportHandler(w http.ResponseWriter, r *http.Re
return
}
report, err := template.ParseFiles(path.Join(server.config.StaticDir, "static", "reports", "UsageReport.html"))
if err != nil {
if err = server.templates.usageReport.Execute(w, bucketRollups); err != nil {
server.log.Error("satellite/console/server: usage report template could not be executed", zap.Error(err))
server.serveError(w, r, http.StatusNotFound)
return
}
err = report.Execute(w, bucketRollups)
}
// accountActivationHandler is web app http handler function
@ -260,19 +305,25 @@ func (server *Server) accountActivationHandler(w http.ResponseWriter, r *http.Re
zap.String("token", activationToken),
zap.Error(err))
server.serveError(w, r)
// TODO: when new error pages will be created - change http.StatusNotFound on appropriate one
server.serveError(w, r, http.StatusNotFound)
return
}
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "activation", "success.html"))
if err = server.templates.activated.Execute(w, nil); err != nil {
server.log.Error("satellite/console/server: account activated template could not be executed", zap.Error(err))
server.serveError(w, r, http.StatusNotFound)
return
}
}
func (server *Server) passwordRecoveryHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
defer mon.Task()(&ctx)(nil)
recoveryToken := r.URL.Query().Get("token")
if len(recoveryToken) == 0 {
server.serveError(w, r)
server.serveError(w, r, http.StatusNotFound)
return
}
@ -280,38 +331,36 @@ func (server *Server) passwordRecoveryHandler(w http.ResponseWriter, r *http.Req
case http.MethodPost:
err := r.ParseForm()
if err != nil {
server.serveError(w, r)
server.serveError(w, r, http.StatusNotFound)
return
}
password := r.FormValue("password")
passwordRepeat := r.FormValue("passwordRepeat")
if strings.Compare(password, passwordRepeat) != 0 {
server.serveError(w, r)
server.serveError(w, r, http.StatusNotFound)
return
}
err = server.service.ResetPassword(ctx, recoveryToken, password)
if err != nil {
server.serveError(w, r)
server.serveError(w, r, http.StatusNotFound)
return
}
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "resetPassword", "success.html"))
if err := server.templates.success.Execute(w, nil); err != nil {
server.log.Error("satellite/console/server: success reset password template could not be executed", zap.Error(err))
server.serveError(w, r, http.StatusNotFound)
return
}
case http.MethodGet:
t, err := template.ParseFiles(filepath.Join(server.config.StaticDir, "static", "resetPassword", "resetPassword.html"))
if err != nil {
server.serveError(w, r)
return
}
err = t.Execute(w, nil)
if err != nil {
server.serveError(w, r)
if err := server.templates.resetPassword.Execute(w, nil); err != nil {
server.log.Error("satellite/console/server: reset password template could not be executed", zap.Error(err))
server.serveError(w, r, http.StatusNotFound)
return
}
default:
server.serveError(w, r)
server.serveError(w, r, http.StatusNotFound)
return
}
}
@ -320,19 +369,24 @@ func (server *Server) cancelPasswordRecoveryHandler(w http.ResponseWriter, r *ht
ctx := r.Context()
defer mon.Task()(&ctx)(nil)
recoveryToken := r.URL.Query().Get("token")
if len(recoveryToken) == 0 {
http.Redirect(w, r, "https://storjlabs.atlassian.net/servicedesk/customer/portals", http.StatusSeeOther)
}
// No need to check error as we anyway redirect user to support page
_ = server.service.RevokeResetPasswordToken(ctx, recoveryToken)
// TODO: Should place this link to config
http.Redirect(w, r, "https://storjlabs.atlassian.net/servicedesk/customer/portals", http.StatusSeeOther)
}
func (server *Server) serveError(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
http.ServeFile(w, r, filepath.Join(server.config.StaticDir, "static", "errors", "404.html"))
func (server *Server) serveError(w http.ResponseWriter, r *http.Request, status int) {
// TODO: show different error pages depend on status
// F.e. switch(status)
// case http.StatusNotFound: server.executeTemplate(w, r, notFound, nil)
// case http.StatusInternalServerError: server.executeTemplate(w, r, internalError, nil)
w.WriteHeader(status)
if err := server.templates.pageNotFound.Execute(w, nil); err != nil {
server.log.Error("error occurred in console/server", zap.Error(err))
}
}
// grapqlHandler is graphql endpoint http handler function
@ -383,34 +437,6 @@ func (server *Server) grapqlHandler(w http.ResponseWriter, r *http.Request) {
sugar.Debug(result)
}
// Run starts the server that host webapp and api endpoint
func (server *Server) Run(ctx context.Context) (err error) {
defer mon.Task()(&ctx)(&err)
server.schema, err = consoleql.CreateSchema(server.log, server.service, server.mailService)
if err != nil {
return Error.Wrap(err)
}
ctx, cancel := context.WithCancel(ctx)
var group errgroup.Group
group.Go(func() error {
<-ctx.Done()
return server.server.Shutdown(nil)
})
group.Go(func() error {
defer cancel()
return server.server.Serve(server.listener)
})
return group.Wait()
}
// Close closes server and underlying listener
func (server *Server) Close() error {
return server.server.Close()
}
// gzipHandler is used to gzip static content to minify resources if browser support such decoding
func (server *Server) gzipHandler(fn http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@ -424,9 +450,14 @@ func (server *Server) gzipHandler(fn http.Handler) http.Handler {
}
isNeededFormatToGzip := formats[extension]
// because we have some static content outside of console frontend app.
// for example: 404 page, account activation, passsrowd reset, etc.
// TODO: find better solution, its a temporary fix
isFromStaticDir := strings.Contains(r.URL.Path, "/static/dist/")
// in case if old browser doesn't support gzip decoding or if file extension is not recommended to gzip
// just return original file
if !isGzipSupported || !isNeededFormatToGzip {
if !isGzipSupported || !isNeededFormatToGzip || !isFromStaticDir {
fn.ServeHTTP(w, r)
return
}
@ -444,3 +475,38 @@ func (server *Server) gzipHandler(fn http.Handler) http.Handler {
fn.ServeHTTP(w, newRequest)
})
}
// initializeTemplates is used to initialize all templates
func (server *Server) initializeTemplates() (err error) {
server.templates.index, err = template.ParseFiles(filepath.Join(server.config.StaticDir, "dist", "index.html"))
if err != nil {
server.log.Error("dist folder is not generated. use 'npm run build' command", zap.Error(err))
}
server.templates.activated, err = template.ParseFiles(filepath.Join(server.config.StaticDir, "static", "activation", "activated.html"))
if err != nil {
return Error.Wrap(err)
}
server.templates.success, err = template.ParseFiles(filepath.Join(server.config.StaticDir, "static", "resetPassword", "success.html"))
if err != nil {
return Error.Wrap(err)
}
server.templates.resetPassword, err = template.ParseFiles(filepath.Join(server.config.StaticDir, "static", "resetPassword", "resetPassword.html"))
if err != nil {
return Error.Wrap(err)
}
server.templates.usageReport, err = template.ParseFiles(path.Join(server.config.StaticDir, "static", "reports", "usageReport.html"))
if err != nil {
return Error.Wrap(err)
}
server.templates.pageNotFound, err = template.ParseFiles(path.Join(server.config.StaticDir, "static", "errors", "404.html"))
if err != nil {
return Error.Wrap(err)
}
return nil
}

View File

@ -8,7 +8,7 @@
<title>Tardigrade Satellite - Mars</title>
<link href="/static/static/fonts/font_regular.ttf" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="/static/static/activation/success.css">
<link rel="stylesheet" type="text/css" href="/static/static/activation/activated.css">
</head>
<body>
<div class="container">

View File

@ -1,48 +0,0 @@
/*Copyright (C) 2019 Storj Labs, Inc.*/
/*See LICENSE for copying information.*/
.error-container {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
width: 100%;
height: 100vh;
background-color: #fff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
h1 {
font-family: 'font', sans-serif;
margin-bottom: 60px;
font-size: 32px;
}
img, a {
-webkit-user-drag: none;
-khtml-user-drag: none;
-moz-user-drag: none;
-o-user-drag: none;
user-drag: none;
}
.logo {
display: flex;
align-items: center;
position: absolute;
top: 87px;
left: 100px;
}
.text {
margin-left: 15px;
}

File diff suppressed because one or more lines are too long