diff --git a/.github/workflows/test-e2e-pr.yml b/.github/workflows/test-e2e-pr.yml index f997bfbc..c5a3eb79 100644 --- a/.github/workflows/test-e2e-pr.yml +++ b/.github/workflows/test-e2e-pr.yml @@ -114,29 +114,23 @@ jobs: azd env set DEPLOY_AZURE_COSMOSDB true azd env set AZURE_COSMOSDB_ACCOUNT_KIND MongoDB azd env set DEPLOY_OBSERVABILITY_TOOLS true + azd env set COMPANY_NAME "Contoso" azd up env: AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }} AZURE_LOCATION: ${{ vars.AZURE_LOCATION }} AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - name: Get endpoint URLs - id: azd_get_endpoint_urls - run: | - echo "STORE_ADMIN_URL=$(azd env get-value SERVICE_STORE_ADMIN_ENDPOINT_URL)" >> "$GITHUB_OUTPUT" - echo "STORE_FRONT_URL=$(azd env get-value SERVICE_STORE_FRONT_ENDPOINT_URL)" >> "$GITHUB_OUTPUT" - name: Install Playwright dependencies run: npm ci working-directory: tests - name: Run Playwright tests - run: npx playwright test --config=playwright.service.config.ts --workers=20 + run: npx playwright test e2e/store-front/basic.spec.ts e2e/store-admin/basic.spec.ts --config=playwright.service.config.ts --workers=20 working-directory: tests env: PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }} - STORE_ADMIN_URL: ${{ steps.azd_get_endpoint_urls.outputs.STORE_ADMIN_URL }} - STORE_FRONT_URL: ${{ steps.azd_get_endpoint_urls.outputs.STORE_FRONT_URL }} CI: true - name: Clean up resources diff --git a/.github/workflows/test-e2e.yaml b/.github/workflows/test-e2e.yaml index e4000464..70f67308 100644 --- a/.github/workflows/test-e2e.yaml +++ b/.github/workflows/test-e2e.yaml @@ -82,7 +82,7 @@ jobs: working-directory: tests - name: Run Playwright tests - run: npx playwright test --config=playwright.service.config.ts --workers=20 + run: npx playwright test e2e/store-front/basic.spec.ts e2e/store-admin/basic.spec.ts --config=playwright.service.config.ts --workers=20 working-directory: tests env: PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }} diff --git a/.github/workflows/test-playwright.yaml b/.github/workflows/test-playwright.yaml index c3869486..64519528 100644 --- a/.github/workflows/test-playwright.yaml +++ b/.github/workflows/test-playwright.yaml @@ -30,7 +30,7 @@ jobs: working-directory: tests - name: Run Playwright tests - run: npx playwright test --config=playwright.service.config.ts --workers=20 + run: npx playwright test e2e/store-front/basic.spec.ts e2e/store-admin/basic.spec.ts --config=playwright.service.config.ts --workers=20 working-directory: tests env: PLAYWRIGHT_SERVICE_URL: ${{ secrets.PLAYWRIGHT_SERVICE_URL }} diff --git a/azure.yaml b/azure.yaml index d3777b05..47570f4b 100644 --- a/azure.yaml +++ b/azure.yaml @@ -51,6 +51,9 @@ services: dir: ../../kustomize/overlays/azd/product-service edits: - set image product-service=${SERVICE_PRODUCT_SERVICE_IMAGE_NAME} + env: + AI_SERVICE_URL: "http://ai-service:5001/" + COMPANY_NAME: ${COMPANY_NAME} hooks: postdeploy: posix: @@ -141,6 +144,8 @@ services: dir: ../../kustomize/overlays/azd/store-front edits: - set image store-front=${SERVICE_STORE_FRONT_IMAGE_NAME} + env: + COMPANY_NAME: ${COMPANY_NAME} hooks: postdeploy: posix: @@ -166,6 +171,8 @@ services: dir: ../../kustomize/overlays/azd/store-admin edits: - set image store-admin=${SERVICE_STORE_ADMIN_IMAGE_NAME} + env: + COMPANY_NAME: ${COMPANY_NAME} hooks: postdeploy: posix: diff --git a/kustomize/overlays/azd/product-service/deployment.yaml b/kustomize/overlays/azd/product-service/deployment.yaml index 0c639595..cebbd643 100644 --- a/kustomize/overlays/azd/product-service/deployment.yaml +++ b/kustomize/overlays/azd/product-service/deployment.yaml @@ -19,9 +19,9 @@ spec: image: product-service:latest ports: - containerPort: 3002 - env: - - name: AI_SERVICE_URL - value: "http://ai-service:5001/" + envFrom: + - configMapRef: + name: product-service resources: requests: cpu: 1m diff --git a/kustomize/overlays/azd/product-service/kustomization.yaml b/kustomize/overlays/azd/product-service/kustomization.yaml index a33121c3..95fb99f5 100644 --- a/kustomize/overlays/azd/product-service/kustomization.yaml +++ b/kustomize/overlays/azd/product-service/kustomization.yaml @@ -3,3 +3,7 @@ kind: Kustomization resources: - deployment.yaml - service.yaml +configMapGenerator: + - envs: + - .env + name: product-service \ No newline at end of file diff --git a/kustomize/overlays/azd/store-admin/deployment.yaml b/kustomize/overlays/azd/store-admin/deployment.yaml index f84ffaef..4e884d9e 100644 --- a/kustomize/overlays/azd/store-admin/deployment.yaml +++ b/kustomize/overlays/azd/store-admin/deployment.yaml @@ -20,6 +20,9 @@ spec: ports: - containerPort: 8081 name: store-admin # container images hosted on ghcr.io and will be removed in future releases + envFrom: + - configMapRef: + name: store-admin resources: requests: cpu: 1m diff --git a/kustomize/overlays/azd/store-admin/kustomization.yaml b/kustomize/overlays/azd/store-admin/kustomization.yaml index a33121c3..21e9d935 100644 --- a/kustomize/overlays/azd/store-admin/kustomization.yaml +++ b/kustomize/overlays/azd/store-admin/kustomization.yaml @@ -3,3 +3,7 @@ kind: Kustomization resources: - deployment.yaml - service.yaml +configMapGenerator: + - envs: + - .env + name: store-admin \ No newline at end of file diff --git a/kustomize/overlays/azd/store-front/deployment.yaml b/kustomize/overlays/azd/store-front/deployment.yaml index f238f5e8..8497654a 100644 --- a/kustomize/overlays/azd/store-front/deployment.yaml +++ b/kustomize/overlays/azd/store-front/deployment.yaml @@ -20,6 +20,9 @@ spec: ports: - containerPort: 8080 name: store-front + envFrom: + - configMapRef: + name: store-front resources: requests: cpu: 1m diff --git a/kustomize/overlays/azd/store-front/kustomization.yaml b/kustomize/overlays/azd/store-front/kustomization.yaml index a33121c3..9b55054f 100644 --- a/kustomize/overlays/azd/store-front/kustomization.yaml +++ b/kustomize/overlays/azd/store-front/kustomization.yaml @@ -3,3 +3,7 @@ kind: Kustomization resources: - deployment.yaml - service.yaml +configMapGenerator: + - envs: + - .env + name: store-front \ No newline at end of file diff --git a/src/product-service/src/configuration.rs b/src/product-service/src/configuration.rs index 8599e321..04087ed7 100644 --- a/src/product-service/src/configuration.rs +++ b/src/product-service/src/configuration.rs @@ -9,6 +9,7 @@ pub struct Settings { pub wasm_bin_path: PathBuf, tcp_listener: Option, pub ai_service_url: String, + pub company_name: String, } impl Settings { @@ -17,6 +18,8 @@ impl Settings { var("WASM_RULE_ENGINE_PATH").unwrap_or_else(|_| "./tests/rule_engine.wasm".to_string()); let ai_service_url = std::env::var("AI_SERVICE_URL").unwrap_or_else(|_| "http://127.0.0.1:5001".to_string()); + let company_name = + std::env::var("COMPANY_NAME").unwrap_or_else(|_| "Contoso".to_string()); Settings { max_size: 262_144, log_level: "info".to_string(), @@ -25,6 +28,7 @@ impl Settings { wasm_bin_path: PathBuf::from(wasm_bin_path_env), tcp_listener: None, ai_service_url: ai_service_url.trim_end_matches('/').to_string(), + company_name, } } diff --git a/src/product-service/src/data.rs b/src/product-service/src/data.rs index fc57b9e7..f5ddc9c2 100644 --- a/src/product-service/src/data.rs +++ b/src/product-service/src/data.rs @@ -1,13 +1,13 @@ use crate::configuration::Settings; use crate::model::Product; -pub fn fetch_products(_settings: &Settings) -> Vec { +pub fn fetch_products(settings: &Settings) -> Vec { vec![ Product { id: 1, - name: "Contoso Catnip's Friend".to_string(), + name: format!("{} Catnip's Friend", settings.company_name), price: 9.99, - description: "Watch your feline friend embark on a fishing adventure with Contoso Catnip's Friend toy. Packed with irresistible catnip and dangling fish lure.".to_string(), + description: format!("Watch your feline friend embark on a fishing adventure with {} Catnip's Friend toy. Packed with irresistible catnip and dangling fish lure.", settings.company_name), image: "/catnip.jpg".to_string() }, Product { @@ -61,9 +61,9 @@ pub fn fetch_products(_settings: &Settings) -> Vec { }, Product { id: 9, - name: "Contoso Claw's Crabby Cat Toy".to_string(), + name: format!("{} Claw's Crabby Cat Toy", settings.company_name), price: 3.99, - description: "Watch your cat go crazy for Contoso Claw's Crabby Cat Toy. This crinkly and catnip-filled toy will awaken their hunting instincts and provide endless entertainment.".to_string(), + description: format!("Watch your cat go crazy for {} Claw's Crabby Cat Toy. This crinkly and catnip-filled toy will awaken their hunting instincts and provide endless entertainment.", settings.company_name), image: "/crabby.jpg".to_string() }, Product { diff --git a/src/store-admin/Dockerfile b/src/store-admin/Dockerfile index 4a791144..311e5a74 100644 --- a/src/store-admin/Dockerfile +++ b/src/store-admin/Dockerfile @@ -27,15 +27,25 @@ EXPOSE 8081 # Set the build argument for the app version number ARG APP_VERSION=0.1.0 -# Set the environment variable for the app version number +# Set the environment variables ENV APP_VERSION=$APP_VERSION +ENV COMPANY_NAME=Contoso +ENV PRODUCT_SERVICE_URL=http://product-service:3002/ +ENV MAKELINE_SERVICE_URL=http://makeline-service:3001/ # Copy the nginx configuration template to the container COPY nginx.conf /etc/nginx/conf.d/nginx.conf.template -# Update the nginx configuration to use the app version number -# and Copy the nginx configuration template to the container -RUN envsubst '${APP_VERSION}' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf +# Create a startup script to substitute environment variables in both nginx config and HTML +RUN echo '#!/bin/sh' > /docker-entrypoint.d/30-substitute-env.sh && \ + echo 'envsubst '"'"'${APP_VERSION} ${COMPANY_NAME} ${PRODUCT_SERVICE_URL} ${MAKELINE_SERVICE_URL}'"'"' < /etc/nginx/conf.d/nginx.conf.template > /etc/nginx/conf.d/default.conf' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo '# Update the page title based on company name' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo 'if [ "$COMPANY_NAME" = "Zava" ]; then' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo ' sed -i "s/Pet Store Admin Portal<\/title>/<title>Zava Pet Store Admin Portal<\/title>/g" /usr/share/nginx/html/index.html' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo 'elif [ "$COMPANY_NAME" = "Contoso" ]; then' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo ' sed -i "s/<title>Pet Store Admin Portal<\/title>/<title>Contoso Pet Store Admin Portal<\/title>/g" /usr/share/nginx/html/index.html' >> /docker-entrypoint.d/30-substitute-env.sh && \ + echo 'fi' >> /docker-entrypoint.d/30-substitute-env.sh && \ + chmod +x /docker-entrypoint.d/30-substitute-env.sh # Start the app CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/src/store-admin/README.md b/src/store-admin/README.md index 7b8381fd..5b9b1341 100644 --- a/src/store-admin/README.md +++ b/src/store-admin/README.md @@ -60,3 +60,84 @@ When the app is running, you should see output similar to the following: ``` Open a browser and navigate to `http://localhost:8081/`. You should see the store admin app running. + +# Store Admin - Dynamic Theming + +This store admin application supports dynamic theming based on the `COMPANY_NAME` environment variable. + +## Supported Themes + +- **contoso**: Contoso Pet Store theme (default) - Uses blue accents and Contoso branding +- **zava**: Zava theme - Uses black and white color scheme with Zava branding + +## Environment Variables + +- `COMPANY_NAME`: Set to either "Contoso" or "Zava" to determine the theme (proper casing) +- `VITE_COMPANY_NAME`: Alternative environment variable for development +- `PRODUCT_SERVICE_URL`: URL for the product service +- `MAKELINE_SERVICE_URL`: URL for the makeline service + +## Usage + +### Development + +```bash +# Use Contoso theme (default) +npm run dev + +# Use Zava theme +COMPANY_NAME=Zava npm run dev + +# Or using Vite environment variable +VITE_COMPANY_NAME=Zava npm run dev +``` + +### Docker + +```bash +# Build with Contoso theme +docker build -t store-admin . + +# Run with Zava theme +docker run -p 8081:8081 -e COMPANY_NAME=Zava store-admin + +# Run with Contoso theme +docker run -p 8081:8081 -e COMPANY_NAME=Contoso store-admin +``` + +### Kubernetes + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: store-admin +spec: + template: + spec: + containers: + - name: store-admin + image: store-admin:latest + env: + - name: COMPANY_NAME + value: "Zava" # or "Contoso" +``` + +## Theme Configuration + +Themes are configured in `src/config/themes.ts`. Each theme defines: + +- Logo image and alt text +- Color scheme (primary, secondary, accent colors, etc.) +- Company name +- Page title + +The theme system uses CSS custom properties to dynamically apply colors throughout the application. + +### Supported Values + +The `COMPANY_NAME` environment variable accepts the following values: +- `Contoso` - Applies the Contoso Pet Store theme +- `Zava` - Applies the Zava theme + +Values are case-sensitive and should use proper capitalization as shown above. The system will convert these to lowercase internally to match theme configuration keys. diff --git a/src/store-admin/index.html b/src/store-admin/index.html index aee42ee7..b51f01a4 100644 --- a/src/store-admin/index.html +++ b/src/store-admin/index.html @@ -4,7 +4,7 @@ <meta charset="UTF-8"> <link rel="icon" href="/favicon.ico"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Contoso Pet Store Admin Portal + Pet Store Admin Portal
diff --git a/src/store-admin/nginx.conf b/src/store-admin/nginx.conf index bbb03cca..36bb16ee 100644 --- a/src/store-admin/nginx.conf +++ b/src/store-admin/nginx.conf @@ -1,17 +1,26 @@ server { - listen 8081; - listen [::]:8081; - server_name localhost; - - #access_log /var/log/nginx/host.access.log main; + listen 8081; + server_name localhost; + root /usr/share/nginx/html; + index index.html index.htm; location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; # for spa routing + try_files $uri $uri/ /index.html; + } + + # Provide runtime configuration endpoint + location /api/config { + add_header Content-Type application/json; + return 200 '{"companyName":"${COMPANY_NAME}"}'; + } + + # Health check endpoint + location /health { + add_header Content-Type application/json; + return 200 '{"status":"ok","version":"${APP_VERSION}"}'; } - #error_page 404 /404.html; + #error_page 404 /404.html; # redirect server error pages to the static page /50x.html # @@ -20,11 +29,6 @@ server { root /usr/share/nginx/html; } - location /health { - default_type application/json; - return 200 '{"status":"ok","version":"${APP_VERSION}"}'; - } - location ~ ^/api/makeline/order/(?\w+) { proxy_pass http://makeline-service:3001/order/$id; proxy_http_version 1.1; diff --git a/src/store-admin/package-lock.json b/src/store-admin/package-lock.json index 6317591a..d5272d4d 100644 --- a/src/store-admin/package-lock.json +++ b/src/store-admin/package-lock.json @@ -3927,16 +3927,14 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", "dev": true, - "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { diff --git a/src/store-admin/public/favicon.png b/src/store-admin/public/favicon.png new file mode 100755 index 00000000..10308120 Binary files /dev/null and b/src/store-admin/public/favicon.png differ diff --git a/src/store-admin/public/zava-logo-black.png b/src/store-admin/public/zava-logo-black.png new file mode 100755 index 00000000..10308120 Binary files /dev/null and b/src/store-admin/public/zava-logo-black.png differ diff --git a/src/store-admin/public/zava-logo-white.png b/src/store-admin/public/zava-logo-white.png new file mode 100755 index 00000000..27357ccb Binary files /dev/null and b/src/store-admin/public/zava-logo-white.png differ diff --git a/src/store-admin/public/zava-logo-white.svg b/src/store-admin/public/zava-logo-white.svg new file mode 100755 index 00000000..40195dec --- /dev/null +++ b/src/store-admin/public/zava-logo-white.svg @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/store-admin/src/App.vue b/src/store-admin/src/App.vue index 8dd05251..58e5ef1e 100644 --- a/src/store-admin/src/App.vue +++ b/src/store-admin/src/App.vue @@ -1,10 +1,11 @@