Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions .github/workflows/prod-release-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ on:
type: boolean
default: true
components:
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server,ambient-control-plane,ambient-mcp) - leave empty for all'
description: 'Components to build (comma-separated: frontend,backend,operator,ambient-runner,state-sync,public-api,ambient-api-server,ambient-control-plane,ambient-mcp,ambient-ui) - leave empty for all'
required: false
type: string
default: ''
Expand Down Expand Up @@ -238,7 +238,8 @@ jobs:
{"name":"public-api","context":"./components/public-api","image":"quay.io/ambient_code/vteam_public_api","dockerfile":"./components/public-api/Dockerfile"},
{"name":"ambient-api-server","context":"./components/ambient-api-server","image":"quay.io/ambient_code/vteam_api_server","dockerfile":"./components/ambient-api-server/Dockerfile"},
{"name":"ambient-control-plane","context":"./components","image":"quay.io/ambient_code/vteam_control_plane","dockerfile":"./components/ambient-control-plane/Dockerfile"},
{"name":"ambient-mcp","context":"./components/ambient-mcp","image":"quay.io/ambient_code/vteam_mcp","dockerfile":"./components/ambient-mcp/Dockerfile"}
{"name":"ambient-mcp","context":"./components/ambient-mcp","image":"quay.io/ambient_code/vteam_mcp","dockerfile":"./components/ambient-mcp/Dockerfile"},
{"name":"ambient-ui","context":"./components","image":"quay.io/ambient_code/vteam_ambient_ui","dockerfile":"./components/ambient-ui/Dockerfile"}
]'

FORCE_ALL="${{ github.event.inputs.force_build_all }}"
Expand Down Expand Up @@ -624,6 +625,7 @@ jobs:
["public-api"]="public-api:public-api"
["ambient-api-server"]="ambient-api-server:ambient-api-server"
["ambient-control-plane"]="ambient-control-plane:ambient-control-plane"
["ambient-ui"]="ambient-ui:ambient-ui"
)


Expand All @@ -636,7 +638,8 @@ jobs:
"public-api:quay.io/ambient_code/vteam_public_api" \
"ambient-api-server:quay.io/ambient_code/vteam_api_server" \
"ambient-control-plane:quay.io/ambient_code/vteam_control_plane" \
"ambient-mcp:quay.io/ambient_code/vteam_mcp"; do
"ambient-mcp:quay.io/ambient_code/vteam_mcp" \
"ambient-ui:quay.io/ambient_code/vteam_ambient_ui"; do
COMP="${comp_image%%:*}"
IMAGE="${comp_image#*:}"

Expand Down
39 changes: 38 additions & 1 deletion .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- 'components/ambient-cli/**'
- 'components/ambient-sdk/go-sdk/**'
- 'components/frontend/**'
- 'components/ambient-ui/**'
- '.github/scripts/**'
- 'tests/**'
- '.github/workflows/unit-tests.yml'
Expand All @@ -23,6 +24,7 @@
- 'components/ambient-cli/**'
- 'components/ambient-sdk/go-sdk/**'
- 'components/frontend/**'
- 'components/ambient-ui/**'
- '.github/scripts/**'
- 'tests/**'
- '.github/workflows/unit-tests.yml'
Expand Down Expand Up @@ -54,6 +56,7 @@
runner: ${{ steps.filter.outputs.runner }}
cli: ${{ steps.filter.outputs.cli }}
frontend: ${{ steps.filter.outputs.frontend }}
ambient-ui: ${{ steps.filter.outputs.ambient-ui }}
scripts: ${{ steps.filter.outputs.scripts }}
steps:
- name: Checkout code
Expand All @@ -75,6 +78,8 @@
- 'components/ambient-sdk/go-sdk/**'
frontend:
- 'components/frontend/**'
ambient-ui:
- 'components/ambient-ui/**'
scripts:
- '.github/scripts/**'
- 'tests/test_model_discovery.py'
Expand Down Expand Up @@ -307,6 +312,36 @@
- name: Run unit tests with coverage
run: npx vitest run --coverage

ambient-ui:
runs-on: ubuntu-latest

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
needs: detect-changes
if: needs.detect-changes.outputs.ambient-ui == 'true' || github.event_name == 'workflow_dispatch'
name: Ambient UI Unit Tests (Vitest)
defaults:
run:
working-directory: components/ambient-ui

steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
with:
node-version-file: 'components/ambient-ui/package.json'
cache: 'npm'
cache-dependency-path: 'components/ambient-ui/package-lock.json'

- name: Build ambient-sdk (file: dependency)
working-directory: components/ambient-sdk/ts-sdk
run: npm ci && npm run build

- name: Install dependencies
run: npm ci

- name: Run unit tests with coverage
run: npx vitest run --coverage

scripts:
runs-on: ubuntu-latest
needs: detect-changes
Expand All @@ -326,7 +361,7 @@

summary:
runs-on: ubuntu-latest
needs: [detect-changes, backend, api-server, runner, cli, frontend, scripts]
needs: [detect-changes, backend, api-server, runner, cli, frontend, ambient-ui, scripts]
if: always()
steps:
- name: Check overall status
Expand All @@ -338,6 +373,7 @@
"${{ needs.runner.result }}" \
"${{ needs.cli.result }}" \
"${{ needs.frontend.result }}" \
"${{ needs.ambient-ui.result }}" \
"${{ needs.scripts.result }}"; do
if [ "$result" == "failure" ] || [ "$result" == "cancelled" ]; then
failed=true
Expand All @@ -350,6 +386,7 @@
echo " runner: ${{ needs.runner.result }}"
echo " cli: ${{ needs.cli.result }}"
echo " frontend: ${{ needs.frontend.result }}"
echo " ambient-ui: ${{ needs.ambient-ui.result }}"
echo " scripts: ${{ needs.scripts.result }}"
exit 1
fi
Expand Down
33 changes: 29 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-cli deploy clean check-architecture
.PHONY: help setup build-all build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-cli build-ambient-ui deploy clean check-architecture
.PHONY: local-down local-status local-reload-api-server local-up local-clean local-rebuild local-reload-backend local-reload-frontend local-reload-operator
.PHONY: local-dev-token
.PHONY: local-logs local-logs-backend local-logs-frontend local-logs-operator local-shell local-shell-frontend
.PHONY: local-test local-test-dev local-test-quick test-all local-troubleshoot local-port-forward local-stop-port-forward
.PHONY: push-all registry-login setup-hooks remove-hooks lint check-minikube check-kind check-kubectl check-local-context dev-bootstrap kind-rebuild kind-reload-backend kind-reload-frontend kind-reload-operator kind-status kind-login kind-sso-toggle
.PHONY: push-all registry-login setup-hooks remove-hooks lint check-minikube check-kind check-kubectl check-local-context dev-bootstrap kind-rebuild kind-reload-backend kind-reload-frontend kind-reload-operator kind-reload-ambient-ui kind-status kind-login kind-sso-toggle
.PHONY: preflight-cluster preflight dev-env dev
.PHONY: e2e-test e2e-setup e2e-clean deploy-langfuse-openshift
.PHONY: unleash-port-forward unleash-status
Expand Down Expand Up @@ -72,6 +72,7 @@ GITHUB_MCP_IMAGE ?= vteam_credential_github:$(IMAGE_TAG)
JIRA_MCP_IMAGE ?= vteam_credential_jira:$(IMAGE_TAG)
K8S_MCP_IMAGE ?= vteam_credential_k8s:$(IMAGE_TAG)
GOOGLE_MCP_IMAGE ?= vteam_credential_google:$(IMAGE_TAG)
AMBIENT_UI_IMAGE ?= vteam_ambient_ui:$(IMAGE_TAG)

# kind-local overlay always references localhost/vteam_* images.
# Podman produces this prefix natively; for Docker we tag before loading.
Expand Down Expand Up @@ -168,7 +169,7 @@ help: ## Display this help message

##@ Building

build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server build-observability-dashboard ## Build all container images
build-all: build-frontend build-backend build-operator build-runner build-state-sync build-public-api build-api-server build-observability-dashboard build-ambient-ui ## Build all container images

build-frontend: ## Build frontend image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building frontend with $(CONTAINER_ENGINE)..."
Expand All @@ -177,6 +178,14 @@ build-frontend: ## Build frontend image
-t $(FRONTEND_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Frontend built: $(FRONTEND_IMAGE)"

build-ambient-ui: ## Build ambient-ui image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building ambient-ui with $(CONTAINER_ENGINE)..."
@cd components && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
-f ambient-ui/Dockerfile \
--build-arg GIT_COMMIT=$(shell git rev-parse HEAD) \
-t $(AMBIENT_UI_IMAGE) .
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Ambient UI built: $(AMBIENT_UI_IMAGE)"

build-backend: ## Build backend image
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Building backend with $(CONTAINER_ENGINE)..."
@cd components/backend && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) $(BUILD_FLAGS) \
Expand Down Expand Up @@ -1085,6 +1094,22 @@ kind-reload-frontend: check-kind check-kubectl check-local-context ## Rebuild an
@kubectl rollout status deployment/frontend -n $(NAMESPACE) --timeout=60s
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Frontend reloaded"

kind-reload-ambient-ui: check-kind check-kubectl check-local-context ## Rebuild and reload ambient-ui only (kind)
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Rebuilding ambient-ui..."
@cd components && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) \
-f ambient-ui/Dockerfile \
--build-arg GIT_COMMIT=$(shell git rev-parse HEAD) \
-t $(AMBIENT_UI_IMAGE) . $(QUIET_REDIRECT)
@$(CONTAINER_ENGINE) tag $(AMBIENT_UI_IMAGE) localhost/$(AMBIENT_UI_IMAGE) 2>/dev/null || true
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Loading image into kind cluster ($(KIND_CLUSTER_NAME))..."
@$(CONTAINER_ENGINE) save localhost/$(AMBIENT_UI_IMAGE) | \
$(CONTAINER_ENGINE) exec -i $(KIND_CLUSTER_NAME)-control-plane \
ctr --namespace=k8s.io images import -
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Restarting ambient-ui..."
@kubectl rollout restart deployment/ambient-ui -n $(NAMESPACE) $(QUIET_REDIRECT)
@kubectl rollout status deployment/ambient-ui -n $(NAMESPACE) --timeout=60s
@echo "$(COLOR_GREEN)✓$(COLOR_RESET) Ambient UI reloaded"

kind-reload-operator: check-kind check-kubectl check-local-context ## Rebuild and reload operator only (kind)
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Rebuilding operator..."
@cd components/operator && $(CONTAINER_ENGINE) build $(PLATFORM_FLAG) \
Expand Down Expand Up @@ -1300,7 +1325,7 @@ check-architecture: ## Validate build architecture matches host

_kind-load-images: ## Internal: Load images into kind cluster
@echo "$(COLOR_BLUE)▶$(COLOR_RESET) Loading images into kind ($(KIND_CLUSTER_NAME))..."
@for img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE); do \
@for img in $(BACKEND_IMAGE) $(FRONTEND_IMAGE) $(OPERATOR_IMAGE) $(RUNNER_IMAGE) $(STATE_SYNC_IMAGE) $(PUBLIC_API_IMAGE) $(API_SERVER_IMAGE) $(OBSERVABILITY_DASHBOARD_IMAGE) $(AMBIENT_UI_IMAGE); do \
echo " Loading $(KIND_IMAGE_PREFIX)$$img..."; \
if [ -n "$(KIND_HOST)" ] || [ "$(CONTAINER_ENGINE)" = "podman" ]; then \
$(CONTAINER_ENGINE) save $(KIND_IMAGE_PREFIX)$$img | \
Expand Down
35 changes: 35 additions & 0 deletions components/ambient-ui/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# dependencies
/node_modules
/.pnp
.pnp.*
__pycache__

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
67 changes: 67 additions & 0 deletions components/ambient-ui/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Use Red Hat UBI Node.js 20 minimal image for dependencies
FROM registry.access.redhat.com/ubi9/nodejs-20-minimal AS deps

WORKDIR /app

USER 0

# Copy SDK dependency first (it's a file: dependency in package.json)
COPY ambient-sdk/ts-sdk ./ambient-sdk/ts-sdk

# Copy ambient-ui package files
COPY ambient-ui/package.json ambient-ui/package-lock.json* ./
RUN npm ci

# Rebuild the source code only when needed
# Use the full nodejs-20 image (not minimal) for the build stage because
# Next.js 16 Turbopack requires native SWC binaries that depend on glibc.
FROM registry.access.redhat.com/ubi9/nodejs-20 AS builder

USER 0

WORKDIR /app

# Copy node_modules from deps stage
COPY --from=deps /app/node_modules ./node_modules
COPY --from=deps /app/ambient-sdk ./ambient-sdk
COPY ambient-ui/ .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
ENV NEXT_TELEMETRY_DISABLED=1

RUN npm run build

# Prepare standalone output with OpenShift-compatible permissions in the builder
# (the hardened runner image is distroless — no shell or chmod available)
RUN mkdir -p /app-output/public /app-output/.next/static && \
cp -r .next/standalone/. /app-output/ && \
cp -r .next/static/. /app-output/.next/static/ && \
cp -r public/. /app-output/public/ && \
chmod -R g=u /app-output && \
chgrp -R 0 /app-output

# Production image — Red Hat Hardened Image (distroless, ~48MB, 0 CVEs)
FROM registry.access.redhat.com/hi/nodejs:latest AS runner

ARG GIT_COMMIT=unknown

WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

COPY --from=builder /app-output/. ./

USER 1001

EXPOSE 3000

ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
LABEL org.opencontainers.image.revision=$GIT_COMMIT

CMD ["node", "server.js"]
22 changes: 22 additions & 0 deletions components/ambient-ui/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
32 changes: 32 additions & 0 deletions components/ambient-ui/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const compat = new FlatCompat({
baseDirectory: __dirname,
});

const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
{
ignores: [
"node_modules/**",
".next/**",
"out/**",
"build/**",
"next-env.d.ts",
],
},
{
rules: {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": "error",
"react-hooks/exhaustive-deps": "warn",
},
},
];

export default eslintConfig;
14 changes: 14 additions & 0 deletions components/ambient-ui/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const path = require('path')

/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
outputFileTracingRoot: path.resolve(__dirname, '../..'),
transpilePackages: ['ambient-sdk'],
experimental: {
staticGenerationMinPagesPerWorker: 100,
},
}

module.exports = nextConfig
Loading
Loading