satellite/console: optional separate web app server
This change creates the ability to run a server separate from the console web server to serve the front end app. You can run it with `satellite run ui`. Since there are now potentially two servers instead of one, the UI server has the option to act as a reverse proxy to the api server for local development by setting `--console.address` to the console backend address and `--console.backend-reverse-proxy` to the console backend's http url. Also, a feature flag has been implemented on the api server to retain the ability to serve the front end app. It is toggled with `--console.frontend-enable`. github issue: https://github.com/storj/storj/issues/5843 Change-Id: I0d30451a20636e3184110dbe28c8a2a8a9505804
This commit is contained in:
parent
9370bc4580
commit
7e03ccfa46
@ -100,6 +100,11 @@ var (
|
|||||||
Short: "Run the satellite API",
|
Short: "Run the satellite API",
|
||||||
RunE: cmdAPIRun,
|
RunE: cmdAPIRun,
|
||||||
}
|
}
|
||||||
|
runUICmd = &cobra.Command{
|
||||||
|
Use: "ui",
|
||||||
|
Short: "Run the satellite UI",
|
||||||
|
RunE: cmdUIRun,
|
||||||
|
}
|
||||||
runRepairerCmd = &cobra.Command{
|
runRepairerCmd = &cobra.Command{
|
||||||
Use: "repair",
|
Use: "repair",
|
||||||
Short: "Run the repair service",
|
Short: "Run the repair service",
|
||||||
@ -366,6 +371,7 @@ func init() {
|
|||||||
rootCmd.AddCommand(runCmd)
|
rootCmd.AddCommand(runCmd)
|
||||||
runCmd.AddCommand(runMigrationCmd)
|
runCmd.AddCommand(runMigrationCmd)
|
||||||
runCmd.AddCommand(runAPICmd)
|
runCmd.AddCommand(runAPICmd)
|
||||||
|
runCmd.AddCommand(runUICmd)
|
||||||
runCmd.AddCommand(runAdminCmd)
|
runCmd.AddCommand(runAdminCmd)
|
||||||
runCmd.AddCommand(runRepairerCmd)
|
runCmd.AddCommand(runRepairerCmd)
|
||||||
runCmd.AddCommand(runAuditorCmd)
|
runCmd.AddCommand(runAuditorCmd)
|
||||||
@ -404,6 +410,7 @@ func init() {
|
|||||||
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
process.Bind(runMigrationCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runMigrationCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
process.Bind(runAPICmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runAPICmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
|
process.Bind(runUICmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
process.Bind(runAdminCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runAdminCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
process.Bind(runRepairerCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runRepairerCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
process.Bind(runAuditorCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
process.Bind(runAuditorCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
|
||||||
|
47
cmd/satellite/ui.go
Normal file
47
cmd/satellite/ui.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright (C) 2023 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/zeebo/errs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"storj.io/private/process"
|
||||||
|
"storj.io/storj/satellite"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdUIRun(cmd *cobra.Command, args []string) (err error) {
|
||||||
|
ctx, _ := process.Ctx(cmd)
|
||||||
|
log := zap.L()
|
||||||
|
|
||||||
|
runCfg.Debug.Address = *process.DebugAddrFlag
|
||||||
|
|
||||||
|
identity, err := runCfg.Identity.Load()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to load identity.", zap.Error(err))
|
||||||
|
return errs.New("Failed to load identity: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
satAddr := runCfg.Config.Contact.ExternalAddress
|
||||||
|
if satAddr == "" {
|
||||||
|
return errs.New("cannot run satellite ui if contact.external-address is not set")
|
||||||
|
}
|
||||||
|
apiAddress := runCfg.Config.Console.ExternalAddress
|
||||||
|
if apiAddress == "" {
|
||||||
|
apiAddress = runCfg.Config.Console.Address
|
||||||
|
}
|
||||||
|
peer, err := satellite.NewUI(log, identity, &runCfg.Config, process.AtomicLevel(cmd), satAddr, apiAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := process.InitMetricsWithHostname(ctx, log, nil); err != nil {
|
||||||
|
log.Warn("Failed to initialize telemetry batcher on satellite api", zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
runError := peer.Run(ctx)
|
||||||
|
closeError := peer.Close()
|
||||||
|
return errs.Combine(runError, closeError)
|
||||||
|
}
|
@ -66,6 +66,7 @@ type Satellite struct {
|
|||||||
|
|
||||||
Core *satellite.Core
|
Core *satellite.Core
|
||||||
API *satellite.API
|
API *satellite.API
|
||||||
|
UI *satellite.UI
|
||||||
Repairer *satellite.Repairer
|
Repairer *satellite.Repairer
|
||||||
Auditor *satellite.Auditor
|
Auditor *satellite.Auditor
|
||||||
Admin *satellite.Admin
|
Admin *satellite.Admin
|
||||||
@ -172,12 +173,17 @@ type Satellite struct {
|
|||||||
Service *mailservice.Service
|
Service *mailservice.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
Console struct {
|
ConsoleBackend struct {
|
||||||
Listener net.Listener
|
Listener net.Listener
|
||||||
Service *console.Service
|
Service *console.Service
|
||||||
Endpoint *consoleweb.Server
|
Endpoint *consoleweb.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConsoleFrontend struct {
|
||||||
|
Listener net.Listener
|
||||||
|
Endpoint *consoleweb.Server
|
||||||
|
}
|
||||||
|
|
||||||
NodeStats struct {
|
NodeStats struct {
|
||||||
Endpoint *nodestats.Endpoint
|
Endpoint *nodestats.Endpoint
|
||||||
}
|
}
|
||||||
@ -298,6 +304,11 @@ func (system *Satellite) Run(ctx context.Context) (err error) {
|
|||||||
group.Go(func() error {
|
group.Go(func() error {
|
||||||
return errs2.IgnoreCanceled(system.API.Run(ctx))
|
return errs2.IgnoreCanceled(system.API.Run(ctx))
|
||||||
})
|
})
|
||||||
|
if system.UI != nil {
|
||||||
|
group.Go(func() error {
|
||||||
|
return errs2.IgnoreCanceled(system.UI.Run(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
group.Go(func() error {
|
group.Go(func() error {
|
||||||
return errs2.IgnoreCanceled(system.Repairer.Run(ctx))
|
return errs2.IgnoreCanceled(system.Repairer.Run(ctx))
|
||||||
})
|
})
|
||||||
@ -519,6 +530,15 @@ func (planet *Planet) newSatellite(ctx context.Context, prefix string, index int
|
|||||||
return nil, errs.Wrap(err)
|
return nil, errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only run if front-end endpoints on console back-end server are disabled.
|
||||||
|
var ui *satellite.UI
|
||||||
|
if !config.Console.FrontendEnable {
|
||||||
|
ui, err = planet.newUI(ctx, index, identity, config, api.ExternalAddress, api.Console.Listener.Addr().String())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
adminPeer, err := planet.newAdmin(ctx, index, identity, db, metabaseDB, config, versionInfo)
|
adminPeer, err := planet.newAdmin(ctx, index, identity, db, metabaseDB, config, versionInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(err)
|
return nil, errs.Wrap(err)
|
||||||
@ -548,19 +568,20 @@ func (planet *Planet) newSatellite(ctx context.Context, prefix string, index int
|
|||||||
peer.Mail.EmailReminders.TestSetLinkAddress("http://" + api.Console.Listener.Addr().String() + "/")
|
peer.Mail.EmailReminders.TestSetLinkAddress("http://" + api.Console.Listener.Addr().String() + "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
return createNewSystem(prefix, log, config, peer, api, repairerPeer, auditorPeer, adminPeer, gcBFPeer, rangedLoopPeer), nil
|
return createNewSystem(prefix, log, config, peer, api, ui, repairerPeer, auditorPeer, adminPeer, gcBFPeer, rangedLoopPeer), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNewSystem makes a new Satellite System and exposes the same interface from
|
// createNewSystem makes a new Satellite System and exposes the same interface from
|
||||||
// before we split out the API. In the short term this will help keep all the tests passing
|
// before we split out the API. In the short term this will help keep all the tests passing
|
||||||
// without much modification needed. However long term, we probably want to rework this
|
// without much modification needed. However long term, we probably want to rework this
|
||||||
// so it represents how the satellite will run when it is made up of many processes.
|
// so it represents how the satellite will run when it is made up of many processes.
|
||||||
func createNewSystem(name string, log *zap.Logger, config satellite.Config, peer *satellite.Core, api *satellite.API, repairerPeer *satellite.Repairer, auditorPeer *satellite.Auditor, adminPeer *satellite.Admin, gcBFPeer *satellite.GarbageCollectionBF, rangedLoopPeer *satellite.RangedLoop) *Satellite {
|
func createNewSystem(name string, log *zap.Logger, config satellite.Config, peer *satellite.Core, api *satellite.API, ui *satellite.UI, repairerPeer *satellite.Repairer, auditorPeer *satellite.Auditor, adminPeer *satellite.Admin, gcBFPeer *satellite.GarbageCollectionBF, rangedLoopPeer *satellite.RangedLoop) *Satellite {
|
||||||
system := &Satellite{
|
system := &Satellite{
|
||||||
Name: name,
|
Name: name,
|
||||||
Config: config,
|
Config: config,
|
||||||
Core: peer,
|
Core: peer,
|
||||||
API: api,
|
API: api,
|
||||||
|
UI: ui,
|
||||||
Repairer: repairerPeer,
|
Repairer: repairerPeer,
|
||||||
Auditor: auditorPeer,
|
Auditor: auditorPeer,
|
||||||
Admin: adminPeer,
|
Admin: adminPeer,
|
||||||
@ -655,6 +676,15 @@ func (planet *Planet) newAPI(ctx context.Context, index int, identity *identity.
|
|||||||
return satellite.NewAPI(log, identity, db, metabaseDB, revocationDB, liveAccounting, rollupsWriteCache, &config, versionInfo, nil)
|
return satellite.NewAPI(log, identity, db, metabaseDB, revocationDB, liveAccounting, rollupsWriteCache, &config, versionInfo, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (planet *Planet) newUI(ctx context.Context, index int, identity *identity.FullIdentity, config satellite.Config, satelliteAddr, consoleAPIAddr string) (_ *satellite.UI, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
prefix := "satellite-ui" + strconv.Itoa(index)
|
||||||
|
log := planet.log.Named(prefix)
|
||||||
|
|
||||||
|
return satellite.NewUI(log, identity, &config, nil, satelliteAddr, consoleAPIAddr)
|
||||||
|
}
|
||||||
|
|
||||||
func (planet *Planet) newAdmin(ctx context.Context, index int, identity *identity.FullIdentity, db satellite.DB, metabaseDB *metabase.DB, config satellite.Config, versionInfo version.Info) (_ *satellite.Admin, err error) {
|
func (planet *Planet) newAdmin(ctx context.Context, index int, identity *identity.FullIdentity, db satellite.DB, metabaseDB *metabase.DB, config satellite.Config, versionInfo version.Info) (_ *satellite.Admin, err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ type Auth struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAuth is a constructor for api auth controller.
|
// NewAuth is a constructor for api auth controller.
|
||||||
func NewAuth(log *zap.Logger, service *console.Service, accountFreezeService *console.AccountFreezeService, mailService *mailservice.Service, cookieAuth *consolewebauth.CookieAuth, analytics *analytics.Service, satelliteName string, externalAddress string, letUsKnowURL string, termsAndConditionsURL string, contactInfoURL string, generalRequestURL string) *Auth {
|
func NewAuth(log *zap.Logger, service *console.Service, accountFreezeService *console.AccountFreezeService, mailService *mailservice.Service, cookieAuth *consolewebauth.CookieAuth, analytics *analytics.Service, satelliteName, externalAddress, letUsKnowURL, termsAndConditionsURL, contactInfoURL, generalRequestURL string) *Auth {
|
||||||
return &Auth{
|
return &Auth{
|
||||||
log: log,
|
log: log,
|
||||||
ExternalAddress: externalAddress,
|
ExternalAddress: externalAddress,
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@ -63,9 +64,13 @@ var (
|
|||||||
// Config contains configuration for console web server.
|
// Config contains configuration for console web server.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Address string `help:"server address of the graphql api gateway and frontend app" devDefault:"127.0.0.1:0" releaseDefault:":10100"`
|
Address string `help:"server address of the graphql api gateway and frontend app" devDefault:"127.0.0.1:0" releaseDefault:":10100"`
|
||||||
|
FrontendAddress string `help:"server address of the front-end app" devDefault:"127.0.0.1:0" releaseDefault:":10200"`
|
||||||
|
ExternalAddress string `help:"external endpoint of the satellite if hosted" default:""`
|
||||||
|
FrontendEnable bool `help:"feature flag to toggle whether console back-end server should also serve front-end endpoints" default:"true"`
|
||||||
|
BackendReverseProxy string `help:"the target URL of console back-end reverse proxy for local development when running a UI server" default:""`
|
||||||
|
|
||||||
StaticDir string `help:"path to static resources" default:""`
|
StaticDir string `help:"path to static resources" default:""`
|
||||||
Watch bool `help:"whether to load templates on each request" default:"false" devDefault:"true"`
|
Watch bool `help:"whether to load templates on each request" default:"false" devDefault:"true"`
|
||||||
ExternalAddress string `help:"external endpoint of the satellite if hosted" default:""`
|
|
||||||
|
|
||||||
AuthToken string `help:"auth token needed for access to registration token creation endpoint" default:"" testDefault:"very-secret-token"`
|
AuthToken string `help:"auth token needed for access to registration token creation endpoint" default:"" testDefault:"very-secret-token"`
|
||||||
AuthTokenSecret string `help:"secret used to sign auth tokens" releaseDefault:"" devDefault:"my-suppa-secret-key"`
|
AuthTokenSecret string `help:"secret used to sign auth tokens" releaseDefault:"" devDefault:"my-suppa-secret-key"`
|
||||||
@ -220,7 +225,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
|||||||
packagePlans: packagePlans,
|
packagePlans: packagePlans,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debug("Starting Satellite UI.", zap.Stringer("Address", server.listener.Addr()))
|
logger.Debug("Starting Satellite Console server.", zap.Stringer("Address", server.listener.Addr()))
|
||||||
|
|
||||||
server.cookieAuth = consolewebauth.NewCookieAuth(consolewebauth.CookieSettings{
|
server.cookieAuth = consolewebauth.NewCookieAuth(consolewebauth.CookieSettings{
|
||||||
Name: "_tokenKey",
|
Name: "_tokenKey",
|
||||||
@ -353,7 +358,6 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
|||||||
analyticsRouter.HandleFunc("/event", analyticsController.EventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
analyticsRouter.HandleFunc("/event", analyticsController.EventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
||||||
analyticsRouter.HandleFunc("/page", analyticsController.PageEventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
analyticsRouter.HandleFunc("/page", analyticsController.PageEventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
||||||
|
|
||||||
if server.config.StaticDir != "" {
|
|
||||||
oidc := oidc.NewEndpoint(
|
oidc := oidc.NewEndpoint(
|
||||||
server.nodeURL, server.config.ExternalAddress,
|
server.nodeURL, server.config.ExternalAddress,
|
||||||
logger, oidcService, service,
|
logger, oidcService, service,
|
||||||
@ -366,17 +370,14 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
|||||||
router.Handle("/oauth/v2/userinfo", server.ipRateLimiter.Limit(http.HandlerFunc(oidc.UserInfo))).Methods(http.MethodGet)
|
router.Handle("/oauth/v2/userinfo", server.ipRateLimiter.Limit(http.HandlerFunc(oidc.UserInfo))).Methods(http.MethodGet)
|
||||||
router.Handle("/oauth/v2/clients/{id}", server.withAuth(http.HandlerFunc(oidc.GetClient))).Methods(http.MethodGet)
|
router.Handle("/oauth/v2/clients/{id}", server.withAuth(http.HandlerFunc(oidc.GetClient))).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
router.HandleFunc("/invited", server.handleInvited)
|
||||||
|
router.HandleFunc("/activation", server.accountActivationHandler)
|
||||||
|
router.HandleFunc("/cancel-password-recovery", server.cancelPasswordRecoveryHandler)
|
||||||
|
|
||||||
|
if server.config.StaticDir != "" && server.config.FrontendEnable {
|
||||||
fs := http.FileServer(http.Dir(server.config.StaticDir))
|
fs := http.FileServer(http.Dir(server.config.StaticDir))
|
||||||
router.PathPrefix("/static/").Handler(server.withCORS(server.brotliMiddleware(http.StripPrefix("/static", fs))))
|
router.PathPrefix("/static/").Handler(server.withCORS(server.brotliMiddleware(http.StripPrefix("/static", fs))))
|
||||||
|
|
||||||
router.HandleFunc("/invited", server.handleInvited)
|
|
||||||
|
|
||||||
// These paths previously required a trailing slash, so we support both forms for now
|
|
||||||
slashRouter := router.NewRoute().Subrouter()
|
|
||||||
slashRouter.StrictSlash(true)
|
|
||||||
slashRouter.HandleFunc("/activation", server.accountActivationHandler)
|
|
||||||
slashRouter.HandleFunc("/cancel-password-recovery", server.cancelPasswordRecoveryHandler)
|
|
||||||
|
|
||||||
if server.config.UseVuetifyProject {
|
if server.config.UseVuetifyProject {
|
||||||
router.PathPrefix("/vuetifypoc").Handler(server.withCORS(http.HandlerFunc(server.vuetifyAppHandler)))
|
router.PathPrefix("/vuetifypoc").Handler(server.withCORS(http.HandlerFunc(server.vuetifyAppHandler)))
|
||||||
}
|
}
|
||||||
@ -427,6 +428,100 @@ func (server *Server) Run(ctx context.Context) (err error) {
|
|||||||
return group.Wait()
|
return group.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFrontendServer creates new instance of console front-end server.
|
||||||
|
// NB: The return type is currently consoleweb.Server, but it does not contain all the dependencies.
|
||||||
|
// It should only be used with RunFrontEnd and Close. We plan on moving this to its own type, but
|
||||||
|
// right now since we have a feature flag to allow the backend server to continue serving the frontend, it
|
||||||
|
// makes it easier if they are the same type.
|
||||||
|
func NewFrontendServer(logger *zap.Logger, config Config, listener net.Listener, nodeURL storj.NodeURL, stripePublicKey string) (server *Server, err error) {
|
||||||
|
server = &Server{
|
||||||
|
log: logger,
|
||||||
|
config: config,
|
||||||
|
listener: listener,
|
||||||
|
nodeURL: nodeURL,
|
||||||
|
stripePublicKey: stripePublicKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debug("Starting Satellite UI server.", zap.Stringer("Address", server.listener.Addr()))
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
|
||||||
|
// N.B. This middleware has to be the first one because it has to be called
|
||||||
|
// the earliest in the HTTP chain.
|
||||||
|
router.Use(newTraceRequestMiddleware(logger, router))
|
||||||
|
|
||||||
|
// in local development, proxy certain requests to the console back-end server
|
||||||
|
if config.BackendReverseProxy != "" {
|
||||||
|
target, err := url.Parse(config.BackendReverseProxy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, Error.Wrap(err)
|
||||||
|
}
|
||||||
|
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||||
|
logger.Debug("Reverse proxy targeting", zap.String("address", config.BackendReverseProxy))
|
||||||
|
|
||||||
|
router.PathPrefix("/api").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}))
|
||||||
|
router.PathPrefix("/oauth").Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
}))
|
||||||
|
router.HandleFunc("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
router.HandleFunc("/invited", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
router.HandleFunc("/activation", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
router.HandleFunc("/cancel-password-recovery", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
router.HandleFunc("/registrationToken/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
router.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
proxy.ServeHTTP(w, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := http.FileServer(http.Dir(server.config.StaticDir))
|
||||||
|
|
||||||
|
router.HandleFunc("/robots.txt", server.seoHandler)
|
||||||
|
router.PathPrefix("/static/").Handler(server.brotliMiddleware(http.StripPrefix("/static", fs)))
|
||||||
|
router.HandleFunc("/config", server.frontendConfigHandler)
|
||||||
|
if server.config.UseVuetifyProject {
|
||||||
|
router.PathPrefix("/vuetifypoc").Handler(http.HandlerFunc(server.vuetifyAppHandler))
|
||||||
|
}
|
||||||
|
router.PathPrefix("/").Handler(http.HandlerFunc(server.appHandler))
|
||||||
|
server.server = http.Server{
|
||||||
|
Handler: server.withRequest(router),
|
||||||
|
MaxHeaderBytes: ContentLengthLimit.Int(),
|
||||||
|
}
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunFrontend starts the server that runs the webapp.
|
||||||
|
func (server *Server) RunFrontend(ctx context.Context) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
var group errgroup.Group
|
||||||
|
group.Go(func() error {
|
||||||
|
<-ctx.Done()
|
||||||
|
return server.server.Shutdown(context.Background())
|
||||||
|
})
|
||||||
|
group.Go(func() error {
|
||||||
|
defer cancel()
|
||||||
|
err := server.server.Serve(server.listener)
|
||||||
|
if errs2.IsCanceled(err) || errors.Is(err, http.ErrServerClosed) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return group.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes server and underlying listener.
|
// Close closes server and underlying listener.
|
||||||
func (server *Server) Close() error {
|
func (server *Server) Close() error {
|
||||||
return server.server.Close()
|
return server.server.Close()
|
||||||
|
@ -5,6 +5,7 @@ package consoleweb_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
@ -219,3 +220,56 @@ func TestUserIDRateLimiter(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusTooManyRequests, applyCouponStatus(firstToken))
|
require.Equal(t, http.StatusTooManyRequests, applyCouponStatus(firstToken))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConsoleBackendWithDisabledFrontEnd(t *testing.T) {
|
||||||
|
testplanet.Run(t, testplanet.Config{
|
||||||
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
||||||
|
Reconfigure: testplanet.Reconfigure{
|
||||||
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||||
|
config.Console.FrontendEnable = false
|
||||||
|
config.Console.UseVuetifyProject = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||||
|
apiAddr := planet.Satellites[0].API.Console.Listener.Addr().String()
|
||||||
|
uiAddr := planet.Satellites[0].UI.Console.Listener.Addr().String()
|
||||||
|
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/", http.StatusNotFound)
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/vuetifypoc", http.StatusNotFound)
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/static/", http.StatusNotFound)
|
||||||
|
|
||||||
|
testEndpoint(ctx, t, uiAddr, "/", http.StatusOK)
|
||||||
|
testEndpoint(ctx, t, uiAddr, "/vuetifypoc", http.StatusOK)
|
||||||
|
testEndpoint(ctx, t, uiAddr, "/static/", http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsoleBackendWithEnabledFrontEnd(t *testing.T) {
|
||||||
|
testplanet.Run(t, testplanet.Config{
|
||||||
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 0,
|
||||||
|
Reconfigure: testplanet.Reconfigure{
|
||||||
|
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
|
||||||
|
config.Console.UseVuetifyProject = true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
||||||
|
apiAddr := planet.Satellites[0].API.Console.Listener.Addr().String()
|
||||||
|
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/", http.StatusOK)
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/vuetifypoc", http.StatusOK)
|
||||||
|
testEndpoint(ctx, t, apiAddr, "/static/", http.StatusOK)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testEndpoint(ctx context.Context, t *testing.T, addr, endpoint string, expectedStatus int) {
|
||||||
|
client := http.Client{}
|
||||||
|
url := "http://" + addr + endpoint
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, http.NoBody)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, err := client.Do(req)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, expectedStatus, result.StatusCode)
|
||||||
|
require.NoError(t, result.Body.Close())
|
||||||
|
}
|
||||||
|
126
satellite/ui.go
Normal file
126
satellite/ui.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Copyright (C) 2019 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package satellite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net"
|
||||||
|
"runtime/pprof"
|
||||||
|
|
||||||
|
"github.com/spacemonkeygo/monkit/v3"
|
||||||
|
"github.com/zeebo/errs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"storj.io/common/identity"
|
||||||
|
"storj.io/common/storj"
|
||||||
|
"storj.io/private/debug"
|
||||||
|
"storj.io/storj/private/lifecycle"
|
||||||
|
"storj.io/storj/satellite/console/consoleweb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UI is the satellite UI process.
|
||||||
|
//
|
||||||
|
// architecture: Peer
|
||||||
|
type UI struct {
|
||||||
|
Log *zap.Logger
|
||||||
|
Identity *identity.FullIdentity
|
||||||
|
DB DB
|
||||||
|
|
||||||
|
Servers *lifecycle.Group
|
||||||
|
|
||||||
|
Debug struct {
|
||||||
|
Listener net.Listener
|
||||||
|
Server *debug.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
Console struct {
|
||||||
|
Listener net.Listener
|
||||||
|
Server *consoleweb.Server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUI creates a new satellite UI process.
|
||||||
|
func NewUI(log *zap.Logger, full *identity.FullIdentity, config *Config, atomicLogLevel *zap.AtomicLevel, satelliteAddr, consoleBackendAddr string) (*UI, error) {
|
||||||
|
peer := &UI{
|
||||||
|
Log: log,
|
||||||
|
Identity: full,
|
||||||
|
|
||||||
|
Servers: lifecycle.NewGroup(log.Named("servers")),
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // setup debug
|
||||||
|
var err error
|
||||||
|
if config.Debug.Address != "" {
|
||||||
|
peer.Debug.Listener, err = net.Listen("tcp", config.Debug.Address)
|
||||||
|
if err != nil {
|
||||||
|
withoutStack := errors.New(err.Error())
|
||||||
|
peer.Log.Debug("failed to start debug endpoints", zap.Error(withoutStack))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debugConfig := config.Debug
|
||||||
|
debugConfig.ControlTitle = "UI"
|
||||||
|
peer.Debug.Server = debug.NewServerWithAtomicLevel(log.Named("debug"), peer.Debug.Listener, monkit.Default, debugConfig, atomicLogLevel)
|
||||||
|
peer.Servers.Add(lifecycle.Item{
|
||||||
|
Name: "debug",
|
||||||
|
Run: peer.Debug.Server.Run,
|
||||||
|
Close: peer.Debug.Server.Close,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
{ // setup console
|
||||||
|
consoleConfig := config.Console
|
||||||
|
|
||||||
|
peer.Console.Listener, err = net.Listen("tcp", consoleConfig.FrontendAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Combine(err, peer.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.Console.Server, err = consoleweb.NewFrontendServer(
|
||||||
|
peer.Log.Named("console:endpoint"),
|
||||||
|
consoleConfig,
|
||||||
|
peer.Console.Listener,
|
||||||
|
storj.NodeURL{ID: peer.ID(), Address: satelliteAddr},
|
||||||
|
config.Payments.StripeCoinPayments.StripePublicKey,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Combine(err, peer.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
peer.Servers.Add(lifecycle.Item{
|
||||||
|
Name: "console:endpoint",
|
||||||
|
Run: peer.Console.Server.RunFrontend,
|
||||||
|
Close: peer.Console.Server.Close,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs satellite UI until it's either closed or it errors.
|
||||||
|
func (peer *UI) Run(ctx context.Context) (err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
|
group, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
pprof.Do(ctx, pprof.Labels("subsystem", "ui"), func(ctx context.Context) {
|
||||||
|
peer.Servers.Run(ctx, group)
|
||||||
|
|
||||||
|
pprof.Do(ctx, pprof.Labels("name", "subsystem-wait"), func(ctx context.Context) {
|
||||||
|
err = group.Wait()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes all the resources.
|
||||||
|
func (peer *UI) Close() error {
|
||||||
|
return peer.Servers.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID returns the peer ID.
|
||||||
|
func (peer *UI) ID() storj.NodeID { return peer.Identity.ID }
|
9
scripts/testdata/satellite-config.yaml.lock
vendored
9
scripts/testdata/satellite-config.yaml.lock
vendored
@ -187,6 +187,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# secret used to sign auth tokens
|
# secret used to sign auth tokens
|
||||||
# console.auth-token-secret: ""
|
# console.auth-token-secret: ""
|
||||||
|
|
||||||
|
# the target URL of console back-end reverse proxy for local development when running a UI server
|
||||||
|
# console.backend-reverse-proxy: ""
|
||||||
|
|
||||||
# url link for for beta satellite feedback
|
# url link for for beta satellite feedback
|
||||||
# console.beta-satellite-feedback-url: ""
|
# console.beta-satellite-feedback-url: ""
|
||||||
|
|
||||||
@ -262,6 +265,12 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0
|
|||||||
# allow domains to embed the satellite in a frame, space separated
|
# allow domains to embed the satellite in a frame, space separated
|
||||||
# console.frame-ancestors: tardigrade.io storj.io
|
# console.frame-ancestors: tardigrade.io storj.io
|
||||||
|
|
||||||
|
# server address of the front-end app
|
||||||
|
# console.frontend-address: :10200
|
||||||
|
|
||||||
|
# feature flag to toggle whether console back-end server should also serve front-end endpoints
|
||||||
|
# console.frontend-enable: true
|
||||||
|
|
||||||
# whether to show new gallery view
|
# whether to show new gallery view
|
||||||
# console.gallery-view-enabled: false
|
# console.gallery-view-enabled: false
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user