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",
|
||||
RunE: cmdAPIRun,
|
||||
}
|
||||
runUICmd = &cobra.Command{
|
||||
Use: "ui",
|
||||
Short: "Run the satellite UI",
|
||||
RunE: cmdUIRun,
|
||||
}
|
||||
runRepairerCmd = &cobra.Command{
|
||||
Use: "repair",
|
||||
Short: "Run the repair service",
|
||||
@ -366,6 +371,7 @@ func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
runCmd.AddCommand(runMigrationCmd)
|
||||
runCmd.AddCommand(runAPICmd)
|
||||
runCmd.AddCommand(runUICmd)
|
||||
runCmd.AddCommand(runAdminCmd)
|
||||
runCmd.AddCommand(runRepairerCmd)
|
||||
runCmd.AddCommand(runAuditorCmd)
|
||||
@ -404,6 +410,7 @@ func init() {
|
||||
process.Bind(runCmd, &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(runUICmd, &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(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
|
||||
API *satellite.API
|
||||
UI *satellite.UI
|
||||
Repairer *satellite.Repairer
|
||||
Auditor *satellite.Auditor
|
||||
Admin *satellite.Admin
|
||||
@ -172,12 +173,17 @@ type Satellite struct {
|
||||
Service *mailservice.Service
|
||||
}
|
||||
|
||||
Console struct {
|
||||
ConsoleBackend struct {
|
||||
Listener net.Listener
|
||||
Service *console.Service
|
||||
Endpoint *consoleweb.Server
|
||||
}
|
||||
|
||||
ConsoleFrontend struct {
|
||||
Listener net.Listener
|
||||
Endpoint *consoleweb.Server
|
||||
}
|
||||
|
||||
NodeStats struct {
|
||||
Endpoint *nodestats.Endpoint
|
||||
}
|
||||
@ -298,6 +304,11 @@ func (system *Satellite) Run(ctx context.Context) (err error) {
|
||||
group.Go(func() error {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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() + "/")
|
||||
}
|
||||
|
||||
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
|
||||
// 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
|
||||
// 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{
|
||||
Name: name,
|
||||
Config: config,
|
||||
Core: peer,
|
||||
API: api,
|
||||
UI: ui,
|
||||
Repairer: repairerPeer,
|
||||
Auditor: auditorPeer,
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
defer mon.Task()(&ctx)(&err)
|
||||
|
||||
|
@ -53,7 +53,7 @@ type Auth struct {
|
||||
}
|
||||
|
||||
// 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{
|
||||
log: log,
|
||||
ExternalAddress: externalAddress,
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -62,10 +63,14 @@ var (
|
||||
|
||||
// Config contains configuration for console web server.
|
||||
type Config struct {
|
||||
Address string `help:"server address of the graphql api gateway and frontend app" devDefault:"127.0.0.1:0" releaseDefault:":10100"`
|
||||
StaticDir string `help:"path to static resources" default:""`
|
||||
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:""`
|
||||
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:""`
|
||||
Watch bool `help:"whether to load templates on each request" default:"false" devDefault:"true"`
|
||||
|
||||
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"`
|
||||
@ -220,7 +225,7 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
||||
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{
|
||||
Name: "_tokenKey",
|
||||
@ -353,30 +358,26 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc
|
||||
analyticsRouter.HandleFunc("/event", analyticsController.EventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
||||
analyticsRouter.HandleFunc("/page", analyticsController.PageEventTriggered).Methods(http.MethodPost, http.MethodOptions)
|
||||
|
||||
if server.config.StaticDir != "" {
|
||||
oidc := oidc.NewEndpoint(
|
||||
server.nodeURL, server.config.ExternalAddress,
|
||||
logger, oidcService, service,
|
||||
server.config.OauthCodeExpiry, server.config.OauthAccessTokenExpiry, server.config.OauthRefreshTokenExpiry,
|
||||
)
|
||||
oidc := oidc.NewEndpoint(
|
||||
server.nodeURL, server.config.ExternalAddress,
|
||||
logger, oidcService, service,
|
||||
server.config.OauthCodeExpiry, server.config.OauthAccessTokenExpiry, server.config.OauthRefreshTokenExpiry,
|
||||
)
|
||||
|
||||
router.HandleFunc("/.well-known/openid-configuration", oidc.WellKnownConfiguration)
|
||||
router.Handle("/oauth/v2/authorize", server.withAuth(http.HandlerFunc(oidc.AuthorizeUser))).Methods(http.MethodPost)
|
||||
router.Handle("/oauth/v2/tokens", server.ipRateLimiter.Limit(http.HandlerFunc(oidc.Tokens))).Methods(http.MethodPost)
|
||||
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.HandleFunc("/.well-known/openid-configuration", oidc.WellKnownConfiguration)
|
||||
router.Handle("/oauth/v2/authorize", server.withAuth(http.HandlerFunc(oidc.AuthorizeUser))).Methods(http.MethodPost)
|
||||
router.Handle("/oauth/v2/tokens", server.ipRateLimiter.Limit(http.HandlerFunc(oidc.Tokens))).Methods(http.MethodPost)
|
||||
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.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))
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (server *Server) Close() error {
|
||||
return server.server.Close()
|
||||
|
@ -5,6 +5,7 @@ package consoleweb_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
@ -219,3 +220,56 @@ func TestUserIDRateLimiter(t *testing.T) {
|
||||
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
|
||||
# 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
|
||||
# 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
|
||||
# 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
|
||||
# console.gallery-view-enabled: false
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user