Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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: 7 additions & 2 deletions .github/workflows/components-build-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- 'components/ambient-api-server/**'
- 'components/ambient-control-plane/**'
- 'components/ambient-mcp/**'
- 'components/ambient-ui/**'
pull_request:
branches: [main, alpha]
paths:
Expand All @@ -27,10 +28,11 @@ on:
- 'components/ambient-api-server/**'
- 'components/ambient-control-plane/**'
- 'components/ambient-mcp/**'
- 'components/ambient-ui/**'
workflow_dispatch:
inputs:
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 @@ -60,7 +62,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"}
]'

SELECTED="${{ github.event.inputs.components }}"
Expand Down Expand Up @@ -384,6 +387,7 @@ jobs:
kustomize edit set image quay.io/ambient_code/vteam_public_api:latest=quay.io/ambient_code/vteam_public_api:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_control_plane:latest=quay.io/ambient_code/vteam_control_plane:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_mcp:latest=quay.io/ambient_code/vteam_mcp:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_ambient_ui:latest=quay.io/ambient_code/vteam_ambient_ui:${{ github.sha }}

- name: Validate kustomization
working-directory: components/manifests/overlays/production
Expand Down Expand Up @@ -462,6 +466,7 @@ jobs:
kustomize edit set image quay.io/ambient_code/vteam_public_api:latest=quay.io/ambient_code/vteam_public_api:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_control_plane:latest=quay.io/ambient_code/vteam_control_plane:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_mcp:latest=quay.io/ambient_code/vteam_mcp:${{ github.sha }}
kustomize edit set image quay.io/ambient_code/vteam_ambient_ui:latest=quay.io/ambient_code/vteam_ambient_ui:${{ github.sha }}

- name: Validate kustomization
working-directory: components/manifests/overlays/production
Expand Down
54 changes: 54 additions & 0 deletions components/ambient-ui/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const path = require('path')

const DEFAULT_PATTERNS = 'localhost:*,127.0.0.1:*'

/**
* Convert a preview-host glob pattern to CSP frame-src source(s).
*
* CSP requires a scheme for host:port patterns, so `localhost:*` becomes
* `http://localhost:* https://localhost:*`. Subdomain wildcards like
* `*.example.com` are valid CSP syntax and pass through unchanged.
*/
function toFrameSrcEntries(pattern) {
if (pattern.includes('://')) {
return [pattern]
}
// CSP only supports a wildcard as the leftmost label (e.g. *.example.com).
// Mid-domain wildcards like *.apps.rosa.*.openshiftapps.com are invalid CSP.
// Collapse to a valid prefix wildcard by keeping everything after the last *.
// e.g. *.apps.rosa.*.openshiftapps.com → *.openshiftapps.com
let cspPattern = pattern
const midWildcard = /\*\.[^*]+\*\./
if (midWildcard.test(pattern)) {
const lastWildIdx = pattern.lastIndexOf('*.')
cspPattern = '*.' + pattern.slice(lastWildIdx + 2)
}
return [`http://${cspPattern}`, `https://${cspPattern}`]
}

/**
* Build the CSP frame-src directive from NEXT_PUBLIC_PREVIEW_ALLOWED_HOSTS.
*/
function buildFrameSrc() {
const raw = process.env.NEXT_PUBLIC_PREVIEW_ALLOWED_HOSTS
const source = (raw && raw.trim()) || DEFAULT_PATTERNS
const patterns = source
.split(',')
.map((p) => p.trim())
.filter((p) => p.length > 0)

const entries = patterns.flatMap(toFrameSrcEntries)
return ["'self'", ...entries].join(' ')
}

/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone',
Expand All @@ -9,6 +50,19 @@ const nextConfig = {
experimental: {
staticGenerationMinPagesPerWorker: 100,
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'Content-Security-Policy',
value: `frame-src ${buildFrameSrc()};`,
},
],
},
]
},
}

module.exports = nextConfig
79 changes: 79 additions & 0 deletions components/ambient-ui/public/preview-bridge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Ambient UI Preview Bridge
*
* Include this script in pages rendered inside the Ambient UI preview iframe
* to enable cross-origin element capture and hover highlighting.
*
* Usage: <script src="/preview-bridge.js"></script>
*
* The bridge listens for postMessage requests from the parent frame and
* responds with element information at the requested coordinates.
*/
(function () {
'use strict';

var currentHighlight = null;

// Element capture: parent asks for the element at (x, y)
window.addEventListener('message', function (e) {
if (!e.data || e.data.type !== 'ambient-capture') return;

var x = e.data.x;
var y = e.data.y;
var el = document.elementFromPoint(x, y);

if (!el) {
e.source.postMessage({ type: 'ambient-captured', html: null, rect: null }, '*');
return;
}

var rect = el.getBoundingClientRect();
e.source.postMessage({
type: 'ambient-captured',
html: el.outerHTML.slice(0, 500),
tagName: el.tagName.toLowerCase(),
id: el.id || null,
className: el.className || null,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
textContent: (el.textContent || '').slice(0, 100),
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
}, '*');
});

// Hover highlight: parent sends cursor position, bridge outlines the element
window.addEventListener('message', function (e) {
if (!e.data || e.data.type !== 'ambient-hover') return;

var el = document.elementFromPoint(e.data.x, e.data.y);

// Remove previous highlight
if (currentHighlight) {
currentHighlight.style.outline = currentHighlight._ambientSavedOutline || '';
delete currentHighlight._ambientSavedOutline;
currentHighlight = null;
}

if (el && el !== document.documentElement && el !== document.body) {
el._ambientSavedOutline = el.style.outline;
el.style.outline = '2px solid #4394e5';
currentHighlight = el;
}

if (el) {
var rect = el.getBoundingClientRect();
e.source.postMessage({
type: 'ambient-hovered',
rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }
}, '*');
}
});

// Clear hover: remove any active highlight
window.addEventListener('message', function (e) {
if (!e.data || e.data.type !== 'ambient-hover-clear') return;
if (currentHighlight) {
currentHighlight.style.outline = currentHighlight._ambientSavedOutline || '';
delete currentHighlight._ambientSavedOutline;
currentHighlight = null;
}
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})();
Loading
Loading