multinode,web/multinode: use go:embed for assets
Go can now directly embed files without relying on external tools. This makes code use go:embed and avoid the external tooling. go:embed requires files to be present in the embedded directory, hence we need to add .keep to "dist" folder. We also add one to public/.keep, such that it won't be deleted when building multinode. Change-Id: I53ac3d5ac76e44f740d95221acf0da99fc256d42
This commit is contained in:
parent
fb7454ed9c
commit
28c9403702
5
Makefile
5
Makefile
@ -175,11 +175,6 @@ multinode-console:
|
|||||||
-u $(shell id -u):$(shell id -g) \
|
-u $(shell id -u):$(shell id -g) \
|
||||||
node:${NODE_VERSION} \
|
node:${NODE_VERSION} \
|
||||||
/bin/bash -c "npm ci && npm run build"
|
/bin/bash -c "npm ci && npm run build"
|
||||||
# embed web assets into go
|
|
||||||
go-bindata -prefix web/multinode/ -fs -o multinode/console/consoleassets/bindata.resource.go -pkg consoleassets web/multinode/dist/... web/multinode/static/...
|
|
||||||
# configure existing go code to know about the new assets
|
|
||||||
/usr/bin/env echo -e '\nfunc init() { FileSystem = AssetFile() }' >> multinode/console/consoleassets/bindata.resource.go
|
|
||||||
gofmt -w -s multinode/console/consoleassets/bindata.resource.go
|
|
||||||
|
|
||||||
.PHONY: satellite-admin-ui
|
.PHONY: satellite-admin-ui
|
||||||
satellite-admin-ui:
|
satellite-admin-ui:
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
// Copyright (C) 2021 Storj Labs, Inc.
|
|
||||||
// See LICENSE for copying information.
|
|
||||||
|
|
||||||
package consoleassets
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// FileSystem is nil by default, but when our Makefile generates
|
|
||||||
// embedded resources via go-bindata, it will also drop an init method
|
|
||||||
// that sets this to not nil.
|
|
||||||
var FileSystem http.FileSystem
|
|
@ -6,8 +6,8 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"html/template"
|
"io"
|
||||||
"io/ioutil"
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -54,7 +54,7 @@ type Server struct {
|
|||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
http http.Server
|
http http.Server
|
||||||
assets http.FileSystem
|
assets fs.FS
|
||||||
|
|
||||||
nodes *nodes.Service
|
nodes *nodes.Service
|
||||||
payouts *payouts.Service
|
payouts *payouts.Service
|
||||||
@ -62,12 +62,10 @@ type Server struct {
|
|||||||
bandwidth *bandwidth.Service
|
bandwidth *bandwidth.Service
|
||||||
storage *storage.Service
|
storage *storage.Service
|
||||||
reputation *reputation.Service
|
reputation *reputation.Service
|
||||||
|
|
||||||
index *template.Template
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer returns new instance of Multinode Dashboard http server.
|
// NewServer returns new instance of Multinode Dashboard http server.
|
||||||
func NewServer(log *zap.Logger, listener net.Listener, assets http.FileSystem, services Services) (*Server, error) {
|
func NewServer(log *zap.Logger, listener net.Listener, assets fs.FS, services Services) (*Server, error) {
|
||||||
server := Server{
|
server := Server{
|
||||||
log: log,
|
log: log,
|
||||||
listener: listener,
|
listener: listener,
|
||||||
@ -134,11 +132,8 @@ func NewServer(log *zap.Logger, listener net.Listener, assets http.FileSystem, s
|
|||||||
reputationRouter := apiRouter.PathPrefix("/reputation").Subrouter()
|
reputationRouter := apiRouter.PathPrefix("/reputation").Subrouter()
|
||||||
reputationRouter.HandleFunc("/satellites/{satelliteID}", reputationController.Stats)
|
reputationRouter.HandleFunc("/satellites/{satelliteID}", reputationController.Stats)
|
||||||
|
|
||||||
if server.assets != nil {
|
router.PathPrefix("/static").Handler(http.FileServer(http.FS(server.assets)))
|
||||||
fs := http.FileServer(server.assets)
|
|
||||||
router.PathPrefix("/static/").Handler(http.StripPrefix("/static", fs))
|
|
||||||
router.PathPrefix("/").HandlerFunc(server.appHandler)
|
router.PathPrefix("/").HandlerFunc(server.appHandler)
|
||||||
}
|
|
||||||
|
|
||||||
server.http = http.Server{
|
server.http = http.Server{
|
||||||
Handler: router,
|
Handler: router,
|
||||||
@ -155,24 +150,18 @@ func (server *Server) appHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
header.Set("X-Content-Type-Options", "nosniff")
|
header.Set("X-Content-Type-Options", "nosniff")
|
||||||
header.Set("Referrer-Policy", "same-origin")
|
header.Set("Referrer-Policy", "same-origin")
|
||||||
|
|
||||||
if server.index == nil {
|
f, err := server.assets.Open("index.html")
|
||||||
server.log.Error("index template is not set")
|
if err != nil {
|
||||||
|
http.Error(w, `web/multinode unbuilt, run "npm install && npm run build" in web/multinode.`, http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
defer func() { _ = f.Close() }()
|
||||||
|
|
||||||
if err := server.index.Execute(w, nil); err != nil {
|
_, _ = io.Copy(w, f)
|
||||||
server.log.Error("index template could not be executed", zap.Error(Error.Wrap(err)))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run starts the server that host webapp and api endpoints.
|
// Run starts the server that host webapp and api endpoints.
|
||||||
func (server *Server) Run(ctx context.Context) (err error) {
|
func (server *Server) Run(ctx context.Context) (err error) {
|
||||||
err = server.initializeTemplates()
|
|
||||||
if err != nil {
|
|
||||||
return Error.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
var group errgroup.Group
|
var group errgroup.Group
|
||||||
|
|
||||||
@ -196,23 +185,3 @@ func (server *Server) Run(ctx context.Context) (err error) {
|
|||||||
func (server *Server) Close() error {
|
func (server *Server) Close() error {
|
||||||
return Error.Wrap(server.http.Close())
|
return Error.Wrap(server.http.Close())
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeTemplates is used to initialize all templates.
|
|
||||||
func (server *Server) initializeTemplates() (err error) {
|
|
||||||
f, err := server.assets.Open("/dist/index.html")
|
|
||||||
if err != nil {
|
|
||||||
server.log.Error("dist folder is not generated. use 'npm run build' command", zap.Error(err))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
b, err := ioutil.ReadAll(f)
|
|
||||||
if err != nil {
|
|
||||||
return Error.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
server.index, err = template.New("index.html").Parse(string(b))
|
|
||||||
if err != nil {
|
|
||||||
return Error.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -5,8 +5,10 @@ package multinode
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"io/fs"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/spacemonkeygo/monkit/v3"
|
"github.com/spacemonkeygo/monkit/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -17,7 +19,6 @@ import (
|
|||||||
"storj.io/common/rpc"
|
"storj.io/common/rpc"
|
||||||
"storj.io/private/debug"
|
"storj.io/private/debug"
|
||||||
"storj.io/storj/multinode/bandwidth"
|
"storj.io/storj/multinode/bandwidth"
|
||||||
"storj.io/storj/multinode/console/consoleassets"
|
|
||||||
"storj.io/storj/multinode/console/server"
|
"storj.io/storj/multinode/console/server"
|
||||||
"storj.io/storj/multinode/nodes"
|
"storj.io/storj/multinode/nodes"
|
||||||
"storj.io/storj/multinode/operators"
|
"storj.io/storj/multinode/operators"
|
||||||
@ -25,6 +26,7 @@ import (
|
|||||||
"storj.io/storj/multinode/reputation"
|
"storj.io/storj/multinode/reputation"
|
||||||
"storj.io/storj/multinode/storage"
|
"storj.io/storj/multinode/storage"
|
||||||
"storj.io/storj/private/lifecycle"
|
"storj.io/storj/private/lifecycle"
|
||||||
|
multinodeweb "storj.io/storj/web/multinode"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -175,10 +177,14 @@ func New(log *zap.Logger, full *identity.FullIdentity, config Config, db DB) (_
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
assets := consoleassets.FileSystem
|
var assets fs.FS
|
||||||
|
assets = multinodeweb.Assets
|
||||||
|
// Use a custom directory instead of the embedded assets.
|
||||||
if config.Console.StaticDir != "" {
|
if config.Console.StaticDir != "" {
|
||||||
// a specific directory has been configured. use it
|
// HACKFIX: Previous setups specify the directory for web/multinode,
|
||||||
assets = http.Dir(config.Console.StaticDir)
|
// instead of the actual built data. This is for backwards compatibility.
|
||||||
|
distDir := filepath.Join(config.Console.StaticDir, "dist")
|
||||||
|
assets = os.DirFS(distDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
peer.Console.Endpoint, err = server.NewServer(
|
peer.Console.Endpoint, err = server.NewServer(
|
||||||
|
23
web/multinode/assets.go
Normal file
23
web/multinode/assets.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (C) 2022 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package multinodeweb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed dist/*
|
||||||
|
var assets embed.FS
|
||||||
|
|
||||||
|
// Assets contains either the built multinode from web/multinode/dist directory
|
||||||
|
// or it is empty.
|
||||||
|
var Assets = func() fs.FS {
|
||||||
|
dist, err := fs.Sub(assets, "dist")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("invalid embedding: %w", err))
|
||||||
|
}
|
||||||
|
return dist
|
||||||
|
}()
|
0
web/multinode/dist/.keep
vendored
Normal file
0
web/multinode/dist/.keep
vendored
Normal file
0
web/multinode/public/.keep
Normal file
0
web/multinode/public/.keep
Normal file
@ -4,17 +4,14 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicPath: '/static/dist',
|
|
||||||
productionSourceMap: false,
|
productionSourceMap: false,
|
||||||
parallel: true,
|
parallel: true,
|
||||||
lintOnSave: false, // disables eslint for builds
|
lintOnSave: false, // disables eslint for builds
|
||||||
|
assetsDir: "static",
|
||||||
configureWebpack: {
|
configureWebpack: {
|
||||||
plugins: [],
|
plugins: [],
|
||||||
},
|
},
|
||||||
chainWebpack: config => {
|
chainWebpack: config => {
|
||||||
config.output.chunkFilename('js/vendors_[hash].js');
|
|
||||||
config.output.filename('js/app_[hash].js');
|
|
||||||
|
|
||||||
config.resolve.alias
|
config.resolve.alias
|
||||||
.set('@', path.resolve('src'));
|
.set('@', path.resolve('src'));
|
||||||
|
|
||||||
@ -22,7 +19,6 @@ module.exports = {
|
|||||||
.plugin('html')
|
.plugin('html')
|
||||||
.tap(args => {
|
.tap(args => {
|
||||||
args[0].template = './index.html';
|
args[0].template = './index.html';
|
||||||
|
|
||||||
return args;
|
return args;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user