From 9b6fcd682eaa202189c23421bb5bf8bd174d3730 Mon Sep 17 00:00:00 2001 From: Ariel Weinberger Date: Mon, 17 Nov 2025 11:47:04 -0600 Subject: [PATCH 1/5] chore: add dockerize profiles action --- .github/workflows/dockertize-profiles.yml | 49 +++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/dockertize-profiles.yml diff --git a/.github/workflows/dockertize-profiles.yml b/.github/workflows/dockertize-profiles.yml new file mode 100644 index 000000000..cc8e68674 --- /dev/null +++ b/.github/workflows/dockertize-profiles.yml @@ -0,0 +1,49 @@ +name: Dockerize Profiles + +on: + push: + branches: [feat-profiles] + pull_request: + types: [opened, synchronize, reopened] + branches: [feat-profiles] + workflow_dispatch: + +jobs: + dockerize-profiles: + runs-on: ubuntu-latest + + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: appwrite/console-cloud + tags: | + type=ref,event=branch,prefix=branch- + type=ref,event=pr + type=sha,prefix=sha- + type=raw,value=gh-${{ github.run_id}} + flavor: | + latest=false + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} From d5bd45e20ea8b7a0f138aa8e82b9b81937c1e2b9 Mon Sep 17 00:00:00 2001 From: Ariel Weinberger Date: Mon, 17 Nov 2025 11:47:40 -0600 Subject: [PATCH 2/5] fix typo --- .../workflows/{dockertize-profiles.yml => dockerize-profiles.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{dockertize-profiles.yml => dockerize-profiles.yml} (100%) diff --git a/.github/workflows/dockertize-profiles.yml b/.github/workflows/dockerize-profiles.yml similarity index 100% rename from .github/workflows/dockertize-profiles.yml rename to .github/workflows/dockerize-profiles.yml From 335c6d40f9dbc3aef483ed9860e754290755bf97 Mon Sep 17 00:00:00 2001 From: Ariel Weinberger Date: Mon, 17 Nov 2025 11:48:17 -0600 Subject: [PATCH 3/5] fix: image name --- .github/workflows/dockerize-profiles.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dockerize-profiles.yml b/.github/workflows/dockerize-profiles.yml index cc8e68674..8e09a234a 100644 --- a/.github/workflows/dockerize-profiles.yml +++ b/.github/workflows/dockerize-profiles.yml @@ -29,7 +29,7 @@ jobs: id: meta uses: docker/metadata-action@v5 with: - images: appwrite/console-cloud + images: appwrite/console-profiles tags: | type=ref,event=branch,prefix=branch- type=ref,event=pr From 029007d64bf3b432fdc533609a290cf7c28091cf Mon Sep 17 00:00:00 2001 From: Ebenezer Don Date: Mon, 17 Nov 2025 18:37:06 +0000 Subject: [PATCH 4/5] Fix Imagine Monaco editor interactions inside Console wrapper (#2488) Co-authored-by: Torsten Dittmann --- .github/workflows/dockerize-profiles.yml | 84 ++++++++++++------------ src/lib/studio/monaco-style-manager.ts | 53 +++++++++++++++ src/lib/studio/studio-widget.ts | 3 + 3 files changed, 98 insertions(+), 42 deletions(-) create mode 100644 src/lib/studio/monaco-style-manager.ts diff --git a/.github/workflows/dockerize-profiles.yml b/.github/workflows/dockerize-profiles.yml index 8e09a234a..aafa95f32 100644 --- a/.github/workflows/dockerize-profiles.yml +++ b/.github/workflows/dockerize-profiles.yml @@ -1,49 +1,49 @@ name: Dockerize Profiles on: - push: - branches: [feat-profiles] - pull_request: - types: [opened, synchronize, reopened] - branches: [feat-profiles] - workflow_dispatch: + push: + branches: [feat-profiles] + pull_request: + types: [opened, synchronize, reopened] + branches: [feat-profiles] + workflow_dispatch: jobs: - dockerize-profiles: - runs-on: ubuntu-latest - - steps: - - name: Checkout the repo - uses: actions/checkout@v2 - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ vars.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} + dockerize-profiles: + runs-on: ubuntu-latest - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v5 - with: - images: appwrite/console-profiles - tags: | - type=ref,event=branch,prefix=branch- - type=ref,event=pr - type=sha,prefix=sha- - type=raw,value=gh-${{ github.run_id}} - flavor: | - latest=false + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Build and push Docker image - id: push - uses: docker/build-push-action@v6 - with: - context: . - push: true - platforms: linux/amd64,linux/arm64 - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: appwrite/console-profiles + tags: | + type=ref,event=branch,prefix=branch- + type=ref,event=pr + type=sha,prefix=sha- + type=raw,value=gh-${{ github.run_id}} + flavor: | + latest=false + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@v6 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/src/lib/studio/monaco-style-manager.ts b/src/lib/studio/monaco-style-manager.ts new file mode 100644 index 000000000..3f71f8fe4 --- /dev/null +++ b/src/lib/studio/monaco-style-manager.ts @@ -0,0 +1,53 @@ +const MONACO_STYLE_ATTRIBUTE = 'data-appwrite-studio-monaco-style'; + +let monacoStyleObserver: MutationObserver | null = null; + +function findMonacoEditorCssLink(): HTMLLinkElement | null { + if (typeof document === 'undefined') { + return null; + } + + const link = document.head?.querySelector( + 'link[rel="stylesheet"][href*="monaco-editor"][href*="editor.main.css"]' + ); + + return link ?? null; +} + +function syncMonacoStyles(shadow: ShadowRoot) { + if (typeof document === 'undefined') { + return; + } + + // Check if already synced + if (shadow.querySelector(`[${MONACO_STYLE_ATTRIBUTE}]`)) { + return; + } + + const link = findMonacoEditorCssLink(); + if (!link) { + return; + } + + const clone = link.cloneNode(true) as HTMLLinkElement; + clone.setAttribute(MONACO_STYLE_ATTRIBUTE, 'true'); + shadow.appendChild(clone); +} + +export function ensureMonacoStyles(shadow: ShadowRoot) { + syncMonacoStyles(shadow); + + if ( + monacoStyleObserver || + typeof MutationObserver === 'undefined' || + typeof document === 'undefined' + ) { + return; + } + + monacoStyleObserver = new MutationObserver(() => { + syncMonacoStyles(shadow); + }); + + monacoStyleObserver.observe(document.head, { childList: true }); +} diff --git a/src/lib/studio/studio-widget.ts b/src/lib/studio/studio-widget.ts index 055014ddc..13ab1caf3 100644 --- a/src/lib/studio/studio-widget.ts +++ b/src/lib/studio/studio-widget.ts @@ -3,6 +3,7 @@ import { app } from '$lib/stores/app'; import { get } from 'svelte/store'; import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; +import { ensureMonacoStyles } from './monaco-style-manager'; import DEV_CSS_URL from '@imagine.dev/web-components/imagine-web-components.css?url'; const COMPONENT_SELECTOR = 'imagine-web-components-wrapper[data-appwrite-studio]'; @@ -83,6 +84,7 @@ function injectStyles(node: HTMLElement, attempt = 0) { } if (shadow.querySelector(`link[${STYLE_ATTRIBUTE}]`)) { + ensureMonacoStyles(shadow); return; } @@ -91,6 +93,7 @@ function injectStyles(node: HTMLElement, attempt = 0) { link.href = DEV_OVERRIDE_WEB_COMPONENTS ? DEV_CSS_URL : CDN_CSS_URL; link.setAttribute(STYLE_ATTRIBUTE, 'true'); shadow.prepend(link); + ensureMonacoStyles(shadow); }) .catch(() => { /* no-op */ From d3062cb51ac7b6ba5f3728260ef47d4b8cef491a Mon Sep 17 00:00:00 2001 From: Torsten Dittmann Date: Mon, 17 Nov 2025 23:33:34 +0400 Subject: [PATCH 5/5] Add Sentry env vars and refactor Sentry init (#2584) Co-authored-by: Ariel Weinberger --- Dockerfile | 13 ++++++ src/hooks.client.ts | 13 ++---- src/hooks.server.ts | 9 ++-- src/lib/sentry.ts | 64 +++++++++++++++++++++++++++++ src/lib/studio/studio-widget.ts | 3 ++ src/lib/studio/studio.svelte | 6 ++- src/routes/(console)/+layout.svelte | 2 +- src/routes/+layout.ts | 3 ++ 8 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 src/lib/sentry.ts diff --git a/Dockerfile b/Dockerfile index 44a6fcae7..821f0410a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,9 +31,16 @@ ARG PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ARG PUBLIC_APPWRITE_ENDPOINT ARG PUBLIC_GROWTH_ENDPOINT ARG PUBLIC_STRIPE_KEY +ARG PUBLIC_SENTRY_DSN +ARG PUBLIC_SENTRY_INCLUDE_PII +ARG PUBLIC_SENTRY_ENVIRONMENT +ARG PUBLIC_SENTRY_TRACES_SAMPLE_RATE +ARG PUBLIC_SENTRY_REPLAY_SAMPLE_RATE +ARG PUBLIC_SENTRY_DEBUG ARG SENTRY_AUTH_TOKEN ARG SENTRY_RELEASE + ENV PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT ENV PUBLIC_GROWTH_ENDPOINT=$PUBLIC_GROWTH_ENDPOINT ENV PUBLIC_CONSOLE_MODE=$PUBLIC_CONSOLE_MODE @@ -44,6 +51,12 @@ ENV PUBLIC_APPWRITE_MULTI_REGION=$PUBLIC_APPWRITE_MULTI_REGION ENV PUBLIC_CONSOLE_EMAIL_VERIFICATION=$PUBLIC_CONSOLE_EMAIL_VERIFICATION ENV PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS=$PUBLIC_CONSOLE_MOCK_AI_SUGGESTIONS ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY +ENV PUBLIC_SENTRY_DSN=$PUBLIC_SENTRY_DSN +ENV PUBLIC_SENTRY_INCLUDE_PII=$PUBLIC_SENTRY_INCLUDE_PII +ENV PUBLIC_SENTRY_ENVIRONMENT=$PUBLIC_SENTRY_ENVIRONMENT +ENV PUBLIC_SENTRY_TRACES_SAMPLE_RATE=$PUBLIC_SENTRY_TRACES_SAMPLE_RATE +ENV PUBLIC_SENTRY_REPLAY_SAMPLE_RATE=$PUBLIC_SENTRY_REPLAY_SAMPLE_RATE +ENV PUBLIC_SENTRY_DEBUG=$PUBLIC_SENTRY_DEBUG ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN ENV SENTRY_RELEASE=$SENTRY_RELEASE ENV PUBLIC_IMAGINE_CDN_URL=$PUBLIC_IMAGINE_CDN_URL diff --git a/src/hooks.client.ts b/src/hooks.client.ts index 4a6565025..2338c2170 100644 --- a/src/hooks.client.ts +++ b/src/hooks.client.ts @@ -1,20 +1,15 @@ -import * as Sentry from '@sentry/sveltekit'; -import { isCloud, isProd } from '$lib/system'; import { AppwriteException } from '@appwrite.io/console'; import type { HandleClientError } from '@sveltejs/kit'; +import { setupSentry } from '$lib/sentry'; -Sentry.init({ - enabled: isCloud && isProd, - dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.us.sentry.io/4504158071422976', - tracesSampleRate: 1, - replaysSessionSampleRate: 0, - replaysOnErrorSampleRate: 0 +setupSentry({ + withSessionReplay: true }); export const handleError: HandleClientError = ({ error, message, status }) => { console.error(error); - let type; + let type: string; if (error instanceof AppwriteException) { status = error.code === 0 ? undefined : error.code; message = error.message; diff --git a/src/hooks.server.ts b/src/hooks.server.ts index e54c183db..ba452c378 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,12 +1,9 @@ import { sequence } from '@sveltejs/kit/hooks'; import { handleErrorWithSentry, sentryHandle } from '@sentry/sveltekit'; -import * as Sentry from '@sentry/sveltekit'; -import { isCloud, isProd } from '$lib/system'; +import { setupSentry } from '$lib/sentry'; -Sentry.init({ - enabled: isCloud && isProd, - dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.us.sentry.io/4504158071422976', - tracesSampleRate: 1.0 +setupSentry({ + withSessionReplay: false }); export const handle = sequence(sentryHandle()); diff --git a/src/lib/sentry.ts b/src/lib/sentry.ts new file mode 100644 index 000000000..e7325e9ba --- /dev/null +++ b/src/lib/sentry.ts @@ -0,0 +1,64 @@ +import { env } from '$env/dynamic/public'; +import * as Sentry from '@sentry/sveltekit'; +import { isCloud } from './system'; + +const timestamp = Date.now(); + +export function getSessionId(userId: string) { + return `${userId}-${timestamp}`; +} + +export function identify(userId: string) { + if (isCloud) { + Sentry.setContext('Imagine Request Context', { + session_id: getSessionId(userId) + }); + Sentry.setUser({ + id: userId + }); + } +} + +export function setupSentry({ withSessionReplay }: { withSessionReplay: boolean }) { + const dsn = env.PUBLIC_SENTRY_DSN; + const environment = env.PUBLIC_SENTRY_ENVIRONMENT; + + if (!dsn) { + return; + } + + if (dsn && !environment) { + throw new Error( + "PUBLIC_SENTRY_ENVIRONMENT is required when PUBLIC_SENTRY_DSN is set. For local development, set it to your name, e.g. 'torsten'." + ); + } + + const variables = { + enabled: true, + includePII: env.PUBLIC_SENTRY_INCLUDE_PII === 'true', + tracesSampleRate: parseFloat(env.PUBLIC_SENTRY_TRACES_SAMPLE_RATE), + replaysSessionSampleRate: parseFloat(env.PUBLIC_SENTRY_REPLAY_SAMPLE_RATE), + debug: env.PUBLIC_SENTRY_DEBUG === 'true' + }; + + const integrations = []; + if (withSessionReplay) { + integrations.push( + Sentry.replayIntegration({ + maskAllText: variables.includePII ? false : true, + maskAllInputs: variables.includePII ? false : true + }) + ); + } + + Sentry.init({ + enabled: variables.enabled, + dsn, + tracesSampleRate: variables.tracesSampleRate, + replaysSessionSampleRate: variables.replaysSessionSampleRate, + replaysOnErrorSampleRate: 1, + integrations, + debug: variables.debug, + sendDefaultPii: true + }); +} diff --git a/src/lib/studio/studio-widget.ts b/src/lib/studio/studio-widget.ts index 13ab1caf3..2063e019f 100644 --- a/src/lib/studio/studio-widget.ts +++ b/src/lib/studio/studio-widget.ts @@ -5,6 +5,7 @@ import { goto } from '$app/navigation'; import { resolve } from '$app/paths'; import { ensureMonacoStyles } from './monaco-style-manager'; import DEV_CSS_URL from '@imagine.dev/web-components/imagine-web-components.css?url'; +import { getSessionId } from '$lib/sentry'; const COMPONENT_SELECTOR = 'imagine-web-components-wrapper[data-appwrite-studio]'; const STYLE_ATTRIBUTE = 'data-appwrite-studio-style'; @@ -273,6 +274,7 @@ export function hideStudio() { export async function initImagine( region: string, projectId: string, + userId: string, callbacks?: { onProjectNameChange: () => void; onAddDomain: () => void | Promise; @@ -291,6 +293,7 @@ export async function initImagine( }, { initialTheme: get(app).themeInUse, + consoleSessionId: getSessionId(userId), callbacks } ); diff --git a/src/lib/studio/studio.svelte b/src/lib/studio/studio.svelte index 69b891bb5..57c0ecd7c 100644 --- a/src/lib/studio/studio.svelte +++ b/src/lib/studio/studio.svelte @@ -18,10 +18,12 @@ const { region, - projectId + projectId, + userId }: { region: string; projectId: string; + userId: string; } = $props(); const siteId = `project-${projectId}`; @@ -31,7 +33,7 @@ onMount(() => { ensureStudioComponent(); - initImagine(region, projectId, { + initImagine(region, projectId, userId, { onProjectNameChange: () => { invalidate(Dependencies.PROJECT); }, diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte index 9a2d98fb2..bc2ca1902 100644 --- a/src/routes/(console)/+layout.svelte +++ b/src/routes/(console)/+layout.svelte @@ -333,7 +333,7 @@ }); - + { .then((response) => [response, null]) .catch((error) => [null, error])) as [Account, AppwriteException]; + if (account) identify(account.$id); + if (url.searchParams.has('forceRedirect')) { redirectTo.set(url.searchParams.get('forceRedirect') || null); url.searchParams.delete('forceRedirect');