diff --git a/Makefile b/Makefile
index cf1c4713d..bcccbe968 100644
--- a/Makefile
+++ b/Makefile
@@ -274,8 +274,9 @@ envtest:
.PHONY: unit-tests
unit-tests: envtest
KUBEBUILDER_ASSETS="$(shell ./operator/bin/setup-envtest use $(ENVTEST_K8S_VERSION) --bin-dir $(shell pwd)/operator/bin -p path)" \
- go test -tags $(GO_BUILD_TAGS) -v ./pkg/... ./cmd/...
+ go test -tags $(GO_BUILD_TAGS) -v ./pkg/... ./cmd/... ./api/... ./web/... ./pkg-new/...
$(MAKE) -C operator test
+ $(MAKE) -C utils unit-tests
.PHONY: vet
vet:
diff --git a/api/api.go b/api/api.go
index f91e9e5e8..484061b3f 100644
--- a/api/api.go
+++ b/api/api.go
@@ -124,7 +124,6 @@ func (a *API) RegisterRoutes(router *mux.Router) {
router.PathPrefix("/swagger/").Handler(httpSwagger.WrapHandler)
router.HandleFunc("/auth/login", a.postAuthLogin).Methods("POST")
- router.HandleFunc("/branding", a.getBranding).Methods("GET")
authenticatedRouter := router.PathPrefix("/").Subrouter()
authenticatedRouter.Use(a.authMiddleware)
diff --git a/api/console.go b/api/console.go
index 3f9d60043..3e7c3a2bc 100644
--- a/api/console.go
+++ b/api/console.go
@@ -2,29 +2,8 @@ package api
import (
"net/http"
-
- "github.com/replicatedhq/embedded-cluster/api/types"
)
-type getBrandingResponse struct {
- Branding types.Branding `json:"branding"`
-}
-
-func (a *API) getBranding(w http.ResponseWriter, r *http.Request) {
- branding, err := a.consoleController.GetBranding()
- if err != nil {
- a.logError(r, err, "failed to get branding")
- a.jsonError(w, r, err)
- return
- }
-
- response := getBrandingResponse{
- Branding: branding,
- }
-
- a.json(w, r, http.StatusOK, response)
-}
-
type getListAvailableNetworkInterfacesResponse struct {
NetworkInterfaces []string `json:"networkInterfaces"`
}
diff --git a/api/controllers/console/controller.go b/api/controllers/console/controller.go
index 61214b06b..66ede9f4e 100644
--- a/api/controllers/console/controller.go
+++ b/api/controllers/console/controller.go
@@ -1,15 +1,10 @@
package console
import (
- "fmt"
-
"github.com/replicatedhq/embedded-cluster/api/pkg/utils"
- "github.com/replicatedhq/embedded-cluster/api/types"
- "github.com/replicatedhq/embedded-cluster/pkg/release"
)
type Controller interface {
- GetBranding() (types.Branding, error)
ListAvailableNetworkInterfaces() ([]string, error)
}
@@ -41,18 +36,6 @@ func NewConsoleController(opts ...ConsoleControllerOption) (*ConsoleController,
return controller, nil
}
-func (c *ConsoleController) GetBranding() (types.Branding, error) {
- app := release.GetApplication()
- if app == nil {
- return types.Branding{}, fmt.Errorf("application not found")
- }
-
- return types.Branding{
- AppTitle: app.Spec.Title,
- AppIcon: app.Spec.Icon,
- }, nil
-}
-
func (c *ConsoleController) ListAvailableNetworkInterfaces() ([]string, error) {
return c.NetUtils.ListValidNetworkInterfaces()
}
diff --git a/api/types/console.go b/api/types/console.go
deleted file mode 100644
index 05ad6d44d..000000000
--- a/api/types/console.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package types
-
-type Branding struct {
- AppTitle string `json:"appTitle"`
- AppIcon string `json:"appIcon"`
-}
diff --git a/api/types/errors_test.go b/api/types/errors_test.go
index dac6f0cfd..b186d0aef 100644
--- a/api/types/errors_test.go
+++ b/api/types/errors_test.go
@@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"net/http"
- "net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
@@ -224,94 +223,3 @@ func TestAPIError_ErrorOrNil(t *testing.T) {
})
}
}
-
-func TestAPIError_JSON(t *testing.T) {
- tests := []struct {
- name string
- apiErr *APIError
- wantCode int
- wantJSON map[string]any
- }{
- {
- name: "simple error",
- apiErr: &APIError{
- StatusCode: http.StatusInternalServerError,
- Message: "invalid request",
- },
- wantCode: http.StatusInternalServerError,
- wantJSON: map[string]any{
- "status_code": float64(http.StatusInternalServerError),
- "message": "invalid request",
- },
- },
- {
- name: "field error",
- apiErr: &APIError{
- StatusCode: http.StatusBadRequest,
- Message: "validation error",
- Field: "username",
- },
- wantCode: http.StatusBadRequest,
- wantJSON: map[string]any{
- "status_code": float64(http.StatusBadRequest),
- "message": "validation error",
- "field": "username",
- },
- },
- {
- name: "error with nested errors",
- apiErr: &APIError{
- StatusCode: http.StatusBadRequest,
- Message: "multiple validation errors",
- Errors: []*APIError{
- {
- Message: "field1 is required",
- Field: "field1",
- },
- {
- Message: "field2 must be a number",
- Field: "field2",
- },
- },
- },
- wantCode: http.StatusBadRequest,
- wantJSON: map[string]any{
- "status_code": float64(http.StatusBadRequest),
- "message": "multiple validation errors",
- "errors": []any{
- map[string]any{
- "message": "field1 is required",
- "field": "field1",
- },
- map[string]any{
- "message": "field2 must be a number",
- "field": "field2",
- },
- },
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- // Create a mock HTTP response recorder
- rec := httptest.NewRecorder()
-
- // Call the JSON method
- tt.apiErr.JSON(rec)
-
- // Check status code
- assert.Equal(t, tt.wantCode, rec.Code, "Status code should match")
-
- // Check content type header
- contentType := rec.Header().Get("Content-Type")
- assert.Equal(t, "application/json", contentType, "Content-Type header should be application/json")
-
- // Parse and check the JSON response
- var gotJSON map[string]any
- err := json.Unmarshal(rec.Body.Bytes(), &gotJSON)
- assert.NoError(t, err, "Should be able to parse the JSON response")
- assert.Equal(t, tt.wantJSON, gotJSON, "JSON response should match expected structure")
- })
- }
-}
diff --git a/cmd/installer/cli/install.go b/cmd/installer/cli/install.go
index d18f75016..9c46d41de 100644
--- a/cmd/installer/cli/install.go
+++ b/cmd/installer/cli/install.go
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "io/fs"
"log"
"net"
"net/http"
@@ -97,6 +98,9 @@ type InstallCmdFlags struct {
tlsKeyBytes []byte
}
+// webAssetsFS is the filesystem to be used by the web component. Defaults to nil allowing the web server to use the default assets embedded in the binary. Useful for testing.
+var webAssetsFS fs.FS = nil
+
// InstallCmd returns a cobra command for installing the embedded cluster.
func InstallCmd(ctx context.Context, name string) *cobra.Command {
var flags InstallCmdFlags
@@ -455,16 +459,21 @@ func runInstallAPI(ctx context.Context, listener net.Listener, cert tls.Certific
if err != nil {
return fmt.Errorf("new api: %w", err)
}
+ app := release.GetApplication()
+ if app == nil {
+ return fmt.Errorf("application not found")
+ }
- api.RegisterRoutes(router.PathPrefix("/api").Subrouter())
-
- var webFs http.Handler
- if os.Getenv("EC_DEV_ENV") == "true" {
- webFs = http.FileServer(http.FS(os.DirFS("./web/dist")))
- } else {
- webFs = http.FileServer(http.FS(web.Fs()))
+ webServer, err := web.New(web.InitialState{
+ Title: app.Spec.Title,
+ Icon: app.Spec.Icon,
+ }, web.WithLogger(logger), web.WithAssetsFS(webAssetsFS))
+ if err != nil {
+ return fmt.Errorf("new web server: %w", err)
}
- router.PathPrefix("/").Methods("GET").Handler(webFs)
+
+ api.RegisterRoutes(router.PathPrefix("/api").Subrouter())
+ webServer.RegisterRoutes(router.PathPrefix("/").Subrouter())
server := &http.Server{
// ErrorLog outputs TLS errors and warnings to the console, we want to make sure we use the same logrus logger for them
diff --git a/cmd/installer/cli/install_test.go b/cmd/installer/cli/install_test.go
index 908d52e56..1304c7144 100644
--- a/cmd/installer/cli/install_test.go
+++ b/cmd/installer/cli/install_test.go
@@ -12,6 +12,7 @@ import (
"os"
"path/filepath"
"testing"
+ "testing/fstest"
"time"
"github.com/replicatedhq/embedded-cluster/api"
@@ -560,6 +561,29 @@ func Test_runInstallAPI(t *testing.T) {
certPool := x509.NewCertPool()
certPool.AddCert(cert.Leaf)
+ // We need a release object to pass over to the Web component.
+ dataMap := map[string][]byte{
+ "kots-app.yaml": []byte(`
+apiVersion: kots.io/v1beta1
+kind: Application
+`),
+ }
+ err = release.SetReleaseDataForTests(dataMap)
+ require.NoError(t, err)
+
+ t.Cleanup(func() {
+ release.SetReleaseDataForTests(nil)
+ })
+
+ // Mock the web assets filesystem so that we don't need to embed the web assets.
+ webAssetsFS = fstest.MapFS{
+ "index.html": &fstest.MapFile{
+ Data: []byte(""),
+ Mode: 0644,
+ },
+ }
+ defer func() { webAssetsFS = nil }()
+
go func() {
err := runInstallAPI(ctx, listener, cert, logger, "password", nil)
t.Logf("Install API exited with error: %v", err)
@@ -581,7 +605,7 @@ func Test_runInstallAPI(t *testing.T) {
},
}
resp, err := httpClient.Get(url)
- assert.NoError(t, err)
+ require.NoError(t, err)
if resp != nil {
defer resp.Body.Close()
}
diff --git a/web/dist/README.md b/web/dist/README.md
index e69de29bb..7f131d6a4 100644
--- a/web/dist/README.md
+++ b/web/dist/README.md
@@ -0,0 +1 @@
+Keeping this file as a placeholder for //got:embed dist to work
diff --git a/web/index.html b/web/index.html
index f61af318d..31c752628 100644
--- a/web/index.html
+++ b/web/index.html
@@ -4,7 +4,11 @@
-
Gitea Enterprise Installer
+ {{ .Title }}
+
+
diff --git a/web/src/components/common/Logo.tsx b/web/src/components/common/Logo.tsx
index a92d850a7..7919dc2fb 100644
--- a/web/src/components/common/Logo.tsx
+++ b/web/src/components/common/Logo.tsx
@@ -3,13 +3,13 @@ import React from 'react';
import { useBranding } from '../../contexts/BrandingContext';
export const AppIcon: React.FC<{ className?: string }> = ({ className = 'w-6 h-6' }) => {
- const { branding } = useBranding();
- if (!branding?.appIcon) {
+ const { icon } = useBranding();
+ if (!icon) {
return ;
}
return (
diff --git a/web/src/components/wizard/CompletionStep.tsx b/web/src/components/wizard/CompletionStep.tsx
index 8e7e33064..16a247b21 100644
--- a/web/src/components/wizard/CompletionStep.tsx
+++ b/web/src/components/wizard/CompletionStep.tsx
@@ -7,14 +7,14 @@ import { CheckCircle, ExternalLink, Copy, ClipboardCheck } from 'lucide-react';
const CompletionStep: React.FC = () => {
const { config, prototypeSettings } = useConfig();
- const { branding } = useBranding();
+ const { title } = useBranding();
const [copied, setCopied] = useState(false);
const themeColor = prototypeSettings.themeColor;
const baseUrl = `${config.useHttps ? 'https' : 'http'}://${config.domain}`;
const urls = [
- { name: 'Web Interface', url: baseUrl, description: `Access the main ${branding?.appTitle} interface` },
- { name: 'API Documentation', url: `${baseUrl}/api/swagger`, description: `Browse and test the ${branding?.appTitle} API` }
+ { name: 'Web Interface', url: baseUrl, description: `Access the main ${title} interface` },
+ { name: 'API Documentation', url: `${baseUrl}/api/swagger`, description: `Browse and test the ${title} API` }
];
const copyToClipboard = (text: string) => {
@@ -33,9 +33,9 @@ const CompletionStep: React.FC = () => {
Installation Complete!
- {branding?.appTitle} is installed successfully.
+ {title} is installed successfully.
-
+