Skip to content

Commit

Permalink
satellite/admin: Serve back-office static UI
Browse files Browse the repository at this point in the history
Serve the front-end sources of the new back-office through the current
satellite admin server under the path `/back-office`.

The front-end is served in the same way than the current one, which is
through an indicated directory path with a configuration parameter or
embed in the binary when that configuration parameter is empty.

The commit also slightly changes the test that checks serving these
static assets for not targeting the empty file in the build folder.

build folders must remain because of the embed directive.

Change-Id: I3c5af6b75ec944722dbdc4c560d0e7d907a205b8
  • Loading branch information
ifraixedes committed Sep 26, 2023
1 parent 48d7be7 commit 6555a68
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 27 deletions.
10 changes: 10 additions & 0 deletions Makefile
Expand Up @@ -73,6 +73,8 @@ build-multinode-npm:
cd web/multinode && npm ci
build-satellite-admin-npm:
cd satellite/admin/ui && npm ci
# Temporary until the new back-office replaces the current admin API & UI
cd satellite/admin/back-office/ui && npm ci

##@ Simulator

Expand Down Expand Up @@ -286,6 +288,14 @@ satellite-admin-ui:
-u $(shell id -u):$(shell id -g) \
node:${NODE_VERSION} \
/bin/bash -c "npm ci && npm run build"
# Temporary until the new back-office replaces the current admin API & UI
docker run --rm -i \
--mount type=bind,src="${PWD}",dst=/go/src/storj.io/storj \
-w /go/src/storj.io/storj/satellite/admin/back-office/ui \
-e HOME=/tmp \
-u $(shell id -u):$(shell id -g) \
node:${NODE_VERSION} \
/bin/bash -c "npm ci && npm run build"

.PHONY: satellite-wasm
satellite-wasm:
Expand Down
3 changes: 2 additions & 1 deletion satellite/admin/back-office/ui/.gitignore
Expand Up @@ -2,4 +2,5 @@ node_modules
.DS_Store
.idea
package-lock.json
dist
/build/*
!/build/.keep
25 changes: 25 additions & 0 deletions satellite/admin/back-office/ui/assets.go
@@ -0,0 +1,25 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

//go:build !noembed
// +build !noembed

package backofficeui

import (
"embed"
"fmt"
"io/fs"
)

//go:embed all:build/*
var assets embed.FS

// Assets contains either the built admin/back-office/ui or it is empty.
var Assets = func() fs.FS {
build, err := fs.Sub(assets, "build")
if err != nil {
panic(fmt.Errorf("invalid embedding: %w", err))
}
return build
}()
18 changes: 18 additions & 0 deletions satellite/admin/back-office/ui/assets_noembed.go
@@ -0,0 +1,18 @@
// Copyright (C) 2023 Storj Labs, Inc.
// See LICENSE for copying information.

//go:build noembed
// +build noembed

package backofficeui

import "io/fs"

// Assets contains either the built admin/back-office/ui or it is empty.
var Assets fs.FS = emptyFS{}

// emptyFS implements an empty filesystem
type emptyFS struct{}

// Open implements fs.FS method.
func (emptyFS) Open(name string) (fs.File, error) { return nil, fs.ErrNotExist }
Empty file.
Empty file.
2 changes: 1 addition & 1 deletion satellite/admin/back-office/ui/src/router/index.js
Expand Up @@ -61,7 +61,7 @@ const routes = [
]

const router = createRouter({
history: createWebHistory(process.env.NODE_ENV === 'production' ? '/admin-ui/' : process.env.BASE_URL),
history: createWebHistory(process.env.NODE_ENV === 'production' ? '/back-office/' : process.env.BASE_URL),
routes,
})

Expand Down
5 changes: 4 additions & 1 deletion satellite/admin/back-office/ui/vite.config.js
Expand Up @@ -11,7 +11,7 @@ import { fileURLToPath, URL } from 'node:url'

// https://vitejs.dev/config/
export default defineConfig({
base: process.env.NODE_ENV === 'production' ? '/admin-ui/' : '/',
base: process.env.NODE_ENV === 'production' ? '/back-office/' : '/',
plugins: [
vue({
template: { transformAssetUrls }
Expand Down Expand Up @@ -42,4 +42,7 @@ export default defineConfig({
server: {
port: 3000,
},
build: {
outDir: "build"
}
})
34 changes: 28 additions & 6 deletions satellite/admin/server.go
Expand Up @@ -20,6 +20,7 @@ import (

"storj.io/common/errs2"
"storj.io/storj/satellite/accounting"
backofficeui "storj.io/storj/satellite/admin/back-office/ui"
adminui "storj.io/storj/satellite/admin/ui"
"storj.io/storj/satellite/attribution"
"storj.io/storj/satellite/buckets"
Expand All @@ -42,10 +43,11 @@ const (

// Config defines configuration for debug server.
type Config struct {
Address string `help:"admin peer http listening address" releaseDefault:"" devDefault:""`
StaticDir string `help:"an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
AllowedOauthHost string `help:"the oauth host allowed to bypass token authentication."`
Groups Groups
Address string `help:"admin peer http listening address" releaseDefault:"" devDefault:""`
StaticDir string `help:"an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
StaticDirBackOffice string `help:"an alternate directory path which contains the static assets for the currently in development back-office. When empty, it uses the embedded assets" releaseDefault:"" devDefault:""`
AllowedOauthHost string `help:"the oauth host allowed to bypass token authentication."`
Groups Groups

AuthorizationToken string `internal:"true"`
}
Expand Down Expand Up @@ -91,7 +93,17 @@ type Server struct {
}

// NewServer returns a new administration Server.
func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.Service, restKeys *restkeys.Service, freezeAccounts *console.AccountFreezeService, accounts payments.Accounts, console consoleweb.Config, config Config) *Server {
func NewServer(
log *zap.Logger,
listener net.Listener,
db DB,
buckets *buckets.Service,
restKeys *restkeys.Service,
freezeAccounts *console.AccountFreezeService,
accounts payments.Accounts,
console consoleweb.Config,
config Config,
) *Server {
server := &Server{
log: log,

Expand Down Expand Up @@ -159,6 +171,17 @@ func NewServer(log *zap.Logger, listener net.Listener, db DB, buckets *buckets.S
limitUpdateAPI.HandleFunc("/projects/{project}/limit", server.getProjectLimit).Methods("GET")
limitUpdateAPI.HandleFunc("/projects/{project}/limit", server.putProjectLimit).Methods("PUT", "POST")

// Temporary path until the new back-office is implemented and we can remove the current admin UI.
if config.StaticDirBackOffice == "" {
root.PathPrefix("/back-office").Handler(
http.StripPrefix("/back-office/", http.FileServer(http.FS(backofficeui.Assets))),
).Methods("GET")
} else {
root.PathPrefix("/back-office").Handler(
http.StripPrefix("/back-office/", http.FileServer(http.Dir(config.StaticDirBackOffice))),
).Methods("GET")
}

// This handler must be the last one because it uses the root as prefix,
// otherwise will try to serve all the handlers set after this one.
if config.StaticDir == "" {
Expand Down Expand Up @@ -214,7 +237,6 @@ func (server *Server) SetAllowedOauthHost(host string) {
func (server *Server) withAuth(allowedGroups []string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

if r.Host != server.config.AllowedOauthHost {
// not behind the proxy; use old authentication method.
if server.config.AuthorizationToken == "" {
Expand Down
58 changes: 41 additions & 17 deletions satellite/admin/server_test.go
Expand Up @@ -25,9 +25,10 @@ func TestBasic(t *testing.T) {
StorageNodeCount: 0,
UplinkCount: 0,
Reconfigure: testplanet.Reconfigure{
Satellite: func(log *zap.Logger, index int, config *satellite.Config) {
Satellite: func(_ *zap.Logger, _ int, config *satellite.Config) {
config.Admin.Address = "127.0.0.1:0"
config.Admin.StaticDir = "ui/build"
config.Admin.StaticDir = "ui"
config.Admin.StaticDirBackOffice = "back-office/ui"
},
},
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
Expand All @@ -36,18 +37,28 @@ func TestBasic(t *testing.T) {
baseURL := "http://" + address.String()

t.Run("UI", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/.keep", nil)
require.NoError(t, err)

response, err := http.DefaultClient.Do(req)
require.NoError(t, err)

require.Equal(t, http.StatusOK, response.StatusCode)

content, err := io.ReadAll(response.Body)
require.NoError(t, response.Body.Close())
require.Empty(t, content)
require.NoError(t, err)
testUI := func(t *testing.T, baseURL string) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/package.json", nil)
require.NoError(t, err)

response, err := http.DefaultClient.Do(req)
require.NoError(t, err)

require.Equal(t, http.StatusOK, response.StatusCode)

content, err := io.ReadAll(response.Body)
require.NoError(t, response.Body.Close())
require.NotEmpty(t, content)
require.Equal(t, byte('{'), content[0])
require.NoError(t, err)
}

t.Run("current", func(t *testing.T) {
testUI(t, baseURL)
})
t.Run("back-office", func(t *testing.T) {
testUI(t, baseURL+"/back-office")
})
})

// Testing authorization behavior without Oauth from here on out.
Expand Down Expand Up @@ -130,7 +141,12 @@ func TestWithOAuth(t *testing.T) {

// Requests that require full access should not be accessible through Oauth.
t.Run("UnauthorizedThroughOauth", func(t *testing.T) {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()), nil)
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()),
nil,
)
require.NoError(t, err)
req.Header.Set("Authorization", planet.Satellites[0].Config.Console.AuthToken)

Expand Down Expand Up @@ -162,7 +178,10 @@ func TestWithOAuth(t *testing.T) {
body, err := io.ReadAll(response.Body)
require.NoError(t, response.Body.Close())
require.NoError(t, err)
errDetail := fmt.Sprintf(admin.UnauthorizedNotInGroup, []string{planet.Satellites[0].Config.Admin.Groups.LimitUpdate})
errDetail := fmt.Sprintf(
admin.UnauthorizedNotInGroup,
[]string{planet.Satellites[0].Config.Admin.Groups.LimitUpdate},
)
require.Contains(t, string(body), errDetail)

req, err = http.NewRequestWithContext(ctx, http.MethodGet, targetURL, nil)
Expand Down Expand Up @@ -200,7 +219,12 @@ func TestWithAuthNoToken(t *testing.T) {
address := sat.Admin.Admin.Listener.Addr()
baseURL := "http://" + address.String()

req, err := http.NewRequestWithContext(ctx, http.MethodGet, fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()), nil)
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
fmt.Sprintf("%s/api/projects/%s/apikeys", baseURL, projectID.String()),
nil,
)
require.NoError(t, err)

// Authorization disabled, so this should fail.
Expand Down
2 changes: 1 addition & 1 deletion satellite/admin/ui/.gitignore
Expand Up @@ -5,4 +5,4 @@ node_modules
/.svelte-kit
/package
.env
.env.*
.env.*
3 changes: 3 additions & 0 deletions scripts/testdata/satellite-config.yaml.lock
Expand Up @@ -25,6 +25,9 @@
# an alternate directory path which contains the static assets to serve. When empty, it uses the embedded assets
# admin.static-dir: ""

# an alternate directory path which contains the static assets for the currently in development back-office. When empty, it uses the embedded assets
# admin.static-dir-back-office: ""

# enable analytics reporting
# analytics.enabled: false

Expand Down

0 comments on commit 6555a68

Please sign in to comment.