fix(dist): load UI resources using embed
parent
16e9412b5d
commit
993f4357c4
6
Makefile
6
Makefile
|
@ -58,12 +58,9 @@ docker: dep assets docker-${BINARY}
|
||||||
|
|
||||||
assets: .jssrc .bindata
|
assets: .jssrc .bindata
|
||||||
|
|
||||||
.bindata: server/swagger.json canned/*.json protoboards/*.json dist/dist_gen.go
|
.bindata: server/swagger.json canned/*.json protoboards/*.json $(UISOURCES)
|
||||||
@touch .bindata
|
@touch .bindata
|
||||||
|
|
||||||
dist/dist_gen.go: $(UISOURCES)
|
|
||||||
go generate -x ./dist
|
|
||||||
|
|
||||||
.jssrc: $(UISOURCES)
|
.jssrc: $(UISOURCES)
|
||||||
cd ui && yarn run clean && yarn run build
|
cd ui && yarn run clean && yarn run build
|
||||||
@touch .jssrc
|
@touch .jssrc
|
||||||
|
@ -142,7 +139,6 @@ clean:
|
||||||
cd ui && yarn run clean
|
cd ui && yarn run clean
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
cd ui && rm -rf node_modules
|
cd ui && rm -rf node_modules
|
||||||
rm -f dist/dist_gen.go
|
|
||||||
@rm -f .godep .jsdep .jssrc .bindata
|
@rm -f .godep .jsdep .jssrc .bindata
|
||||||
|
|
||||||
ctags:
|
ctags:
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
package dist
|
package dist
|
||||||
|
|
||||||
//go:generate go-bindata -o dist_gen.go -ignore '\.map|\.go' -pkg dist ../ui/build/... ../ui/package.json
|
|
||||||
//go:generate go fmt dist_gen.go
|
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
assetfs "github.com/elazarl/go-bindata-assetfs"
|
"github.com/influxdata/chronograf/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DebugAssets serves assets via a specified directory
|
// DebugAssets serves assets via a specified directory
|
||||||
|
@ -23,92 +17,7 @@ func (d *DebugAssets) Handler() http.Handler {
|
||||||
return http.FileServer(NewDir(d.Dir, d.Default))
|
return http.FileServer(NewDir(d.Dir, d.Default))
|
||||||
}
|
}
|
||||||
|
|
||||||
// BindataAssets serves assets from go-bindata, but, also serves Default if assent doesn't exist
|
|
||||||
// This is to support single-page react-apps with its own router.
|
|
||||||
type BindataAssets struct {
|
|
||||||
Prefix string // Prefix is prepended to the http file request
|
|
||||||
Default string // Default is the file to serve if the file is not found
|
|
||||||
DefaultContentType string // DefaultContentType is the content type of the default file
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler serves go-bindata using a go-bindata-assetfs façade
|
|
||||||
func (b *BindataAssets) Handler() http.Handler {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// addCacheHeaders requests an hour of Cache-Control and sets an ETag based on file size and modtime
|
|
||||||
func (b *BindataAssets) addCacheHeaders(filename string, w http.ResponseWriter) error {
|
|
||||||
w.Header().Add("Cache-Control", "public, max-age=3600")
|
|
||||||
|
|
||||||
w.Header().Add("X-Frame-Options", "SAMEORIGIN")
|
|
||||||
w.Header().Add("X-XSS-Protection", "1; mode=block")
|
|
||||||
w.Header().Add("X-Content-Type-Options", "nosniff")
|
|
||||||
w.Header().Add("Content-Security-Policy", "script-src 'self'; object-src 'self'")
|
|
||||||
|
|
||||||
fi, err := AssetInfo(filename)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hour, minute, second := fi.ModTime().Clock()
|
|
||||||
etag := fmt.Sprintf(`"%d%d%d%d%d"`, fi.Size(), fi.ModTime().Day(), hour, minute, second)
|
|
||||||
|
|
||||||
w.Header().Set("ETag", etag)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP wraps http.FileServer by returning a default asset if the asset
|
|
||||||
// doesn't exist. This supports single-page react-apps with its own
|
|
||||||
// built-in router. Additionally, we override the content-type if the
|
|
||||||
// Default file is used.
|
|
||||||
func (b *BindataAssets) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// def wraps the assets to return the default file if the file doesn't exist
|
|
||||||
def := func(name string) ([]byte, error) {
|
|
||||||
// If the named asset exists, then return it directly.
|
|
||||||
octets, err := Asset(name)
|
|
||||||
if err != nil {
|
|
||||||
// If this is at / then we just error out so we can return a Directory
|
|
||||||
// This directory will then be redirected by go to the /index.html
|
|
||||||
if name == b.Prefix {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// If this is anything other than slash, we just return the default
|
|
||||||
// asset. This default asset will handle the routing.
|
|
||||||
// Additionally, because we know we are returning the default asset,
|
|
||||||
// we need to set the default asset's content-type.
|
|
||||||
w.Header().Set("Content-Type", b.DefaultContentType)
|
|
||||||
if err := b.addCacheHeaders(b.Default, w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Asset(b.Default)
|
|
||||||
}
|
|
||||||
if err := b.addCacheHeaders(name, w); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// https://github.com/influxdata/chronograf/issues/5565
|
|
||||||
// workaround wrong .js content-type on windows
|
|
||||||
if strings.HasSuffix(name, ".js") {
|
|
||||||
w.Header().Set("Content-Type", "text/javascript")
|
|
||||||
}
|
|
||||||
return octets, nil
|
|
||||||
}
|
|
||||||
var dir http.FileSystem = &assetfs.AssetFS{
|
|
||||||
Asset: def,
|
|
||||||
AssetDir: AssetDir,
|
|
||||||
AssetInfo: AssetInfo,
|
|
||||||
Prefix: b.Prefix,
|
|
||||||
}
|
|
||||||
http.FileServer(dir).ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
var re = regexp.MustCompile(`"version"\s*:\s*"(.*)"`)
|
|
||||||
|
|
||||||
// GetVersion returns version of the packed assets
|
// GetVersion returns version of the packed assets
|
||||||
func GetVersion() string {
|
func GetVersion() string {
|
||||||
if data, err := Asset("../ui/package.json"); err == nil {
|
return ui.GetVersion()
|
||||||
if matches := re.FindStringSubmatch(string(data)); matches != nil {
|
|
||||||
return matches[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,14 @@ import (
|
||||||
|
|
||||||
"github.com/influxdata/chronograf"
|
"github.com/influxdata/chronograf"
|
||||||
"github.com/influxdata/chronograf/dist"
|
"github.com/influxdata/chronograf/dist"
|
||||||
|
"github.com/influxdata/chronograf/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Dir is prefix of the assets in the bindata
|
|
||||||
Dir = "../ui/build"
|
|
||||||
// Default is the default item to load if 404
|
|
||||||
Default = "../ui/build/index.html"
|
|
||||||
// DebugDir is the prefix of the assets in development mode
|
// DebugDir is the prefix of the assets in development mode
|
||||||
DebugDir = "ui/build"
|
DebugDir = "ui/build"
|
||||||
// DebugDefault is the default item to load if 404
|
// DebugDefault is the default item to load if 404
|
||||||
DebugDefault = "ui/build/index.html"
|
DebugDefault = "ui/build/index.html"
|
||||||
// DefaultContentType is the content-type to return for the Default file
|
|
||||||
DefaultContentType = "text/html; charset=utf-8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AssetsOpts configures the asset middleware
|
// AssetsOpts configures the asset middleware
|
||||||
|
@ -37,11 +32,7 @@ func Assets(opts AssetsOpts) http.Handler {
|
||||||
Default: DebugDefault,
|
Default: DebugDefault,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
assets = &dist.BindataAssets{
|
assets = &ui.BindataAssets{}
|
||||||
Prefix: Dir,
|
|
||||||
Default: Default,
|
|
||||||
DefaultContentType: DefaultContentType,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Default page to load (upon a miss)
|
||||||
|
DefaultPage = "index.html"
|
||||||
|
// DefaultPageContentType is the content-type of the DefaultPage
|
||||||
|
DefaultPageContentType = "text/html; charset=utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed build/*
|
||||||
|
var embeddedFS embed.FS
|
||||||
|
var buildDir fs.FS
|
||||||
|
|
||||||
|
//go:embed package.json
|
||||||
|
var packageJson string
|
||||||
|
var version string
|
||||||
|
|
||||||
|
// init initializes version and buildDir file system
|
||||||
|
func init() {
|
||||||
|
// parse version
|
||||||
|
version = ""
|
||||||
|
re := regexp.MustCompile(`"version"\s*:\s*"(.*)"`)
|
||||||
|
if matches := re.FindStringSubmatch(packageJson); matches != nil {
|
||||||
|
version = matches[1]
|
||||||
|
}
|
||||||
|
// initialize buildDir and default file
|
||||||
|
var err error
|
||||||
|
if buildDir, err = fs.Sub(embeddedFS, "build"); err != nil {
|
||||||
|
panic("no ui/build directory found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindataAssets serves embedded ui assets and also serves its index.html by default
|
||||||
|
// in order to support single-page react-apps with its own router.
|
||||||
|
type BindataAssets struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// fsImpl is a s imple fs.FS implementation that uses the supplied OpenFn function
|
||||||
|
type fsImpl struct {
|
||||||
|
openFn func(path string) (fs.File, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *fsImpl) Open(path string) (fs.File, error) {
|
||||||
|
return fs.openFn(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler returns HTTP handler that serves embedded data
|
||||||
|
func (b *BindataAssets) Handler() http.Handler {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// addCacheHeaders requests an hour of Cache-Control and sets an ETag based on file size and modtime
|
||||||
|
func addCacheHeaders(file fs.File, headers http.Header) error {
|
||||||
|
fi, err := file.Stat()
|
||||||
|
if err == nil {
|
||||||
|
headers.Add("Cache-Control", "public, max-age=3600")
|
||||||
|
|
||||||
|
headers.Add("X-Frame-Options", "SAMEORIGIN")
|
||||||
|
headers.Add("X-XSS-Protection", "1; mode=block")
|
||||||
|
headers.Add("X-Content-Type-Options", "nosniff")
|
||||||
|
headers.Add("Content-Security-Policy", "script-src 'self'; object-src 'self'")
|
||||||
|
|
||||||
|
hour, minute, second := fi.ModTime().Clock()
|
||||||
|
etag := fmt.Sprintf(`"%d%d%d%d%d"`, fi.Size(), fi.ModTime().Day(), hour, minute, second)
|
||||||
|
|
||||||
|
headers.Set("ETag", etag)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP wraps http.FileServer by returning a default asset if the asset
|
||||||
|
// doesn't exist. This supports single-page react-apps with its own
|
||||||
|
// built-in router. Additionally, we override the content-type if the
|
||||||
|
// Default file is used.
|
||||||
|
func (b *BindataAssets) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// openFn wraps buidDir.Open in order to setup default HTTP headers
|
||||||
|
// it also returns the default file if the file doesn't exist
|
||||||
|
openFn := func(name string) (fs.File, error) {
|
||||||
|
// If the named asset exists, then return it directly.
|
||||||
|
file, err := buildDir.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
// If this is at / then we just error out so we can return a Directory
|
||||||
|
// This directory will then be redirected by go to the /index.html
|
||||||
|
if name == "/" || name == "." {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// If this is anything other than slash, we just return the default
|
||||||
|
// asset. This default asset will handle the routing.
|
||||||
|
// Additionally, because we know we are returning the default asset,
|
||||||
|
// we need to set the default asset's content-type.
|
||||||
|
w.Header().Set("Content-Type", DefaultPageContentType)
|
||||||
|
defaultFile, err := buildDir.Open(DefaultPage)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
file = defaultFile
|
||||||
|
}
|
||||||
|
if err := addCacheHeaders(file, w.Header()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// https://github.com/influxdata/chronograf/issues/5565
|
||||||
|
// workaround wrong .js content-type on windows
|
||||||
|
if strings.HasSuffix(name, ".js") {
|
||||||
|
w.Header().Set("Content-Type", "text/javascript")
|
||||||
|
}
|
||||||
|
return file, nil
|
||||||
|
}
|
||||||
|
var fs fs.FS = &fsImpl{
|
||||||
|
openFn: openFn,
|
||||||
|
}
|
||||||
|
http.FileServer(http.FS(fs)).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetVersion returns version of the packed assets
|
||||||
|
func GetVersion() string {
|
||||||
|
return version
|
||||||
|
}
|
Loading…
Reference in New Issue