mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'feat-profiles' of https://github.com/appwrite/console into feat-profiles-polishing
This commit is contained in:
@@ -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-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 }}
|
||||
+13
@@ -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
|
||||
|
||||
+4
-9
@@ -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;
|
||||
|
||||
+3
-6
@@ -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());
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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<HTMLLinkElement>(
|
||||
'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 });
|
||||
}
|
||||
@@ -3,7 +3,9 @@ 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';
|
||||
import { getSessionId } from '$lib/sentry';
|
||||
|
||||
const COMPONENT_SELECTOR = 'imagine-web-components-wrapper[data-appwrite-studio]';
|
||||
const STYLE_ATTRIBUTE = 'data-appwrite-studio-style';
|
||||
@@ -87,6 +89,7 @@ function injectStyles(node: HTMLElement, attempt = 0) {
|
||||
}
|
||||
|
||||
if (shadow.querySelector<HTMLLinkElement>(`link[${STYLE_ATTRIBUTE}]`)) {
|
||||
ensureMonacoStyles(shadow);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,6 +98,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 */
|
||||
@@ -272,6 +276,7 @@ export function hideStudio() {
|
||||
export async function initImagine(
|
||||
region: string,
|
||||
projectId: string,
|
||||
userId: string,
|
||||
callbacks?: {
|
||||
onProjectNameChange: () => void;
|
||||
onAddDomain: () => void | Promise<void>;
|
||||
@@ -290,6 +295,7 @@ export async function initImagine(
|
||||
},
|
||||
{
|
||||
initialTheme: get(app).themeInUse,
|
||||
consoleSessionId: getSessionId(userId),
|
||||
callbacks
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -333,7 +333,7 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Studio region={page.params.region} projectId={page.params.project} />
|
||||
<Studio region={page.params.region} projectId={page.params.project} userId={data.account.$id} />
|
||||
<CommandCenter />
|
||||
<Shell
|
||||
showSideNavigation={page.url.pathname !== '/' &&
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { Account } from '$lib/stores/user';
|
||||
import { type AppwriteException, Query } from '@appwrite.io/console';
|
||||
import { isCloud, VARS } from '$lib/system';
|
||||
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
|
||||
import { identify } from '$lib/sentry';
|
||||
import { resolvedProfile } from '$lib/profiles/index.svelte';
|
||||
|
||||
export const ssr = false;
|
||||
@@ -24,6 +25,8 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
|
||||
.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');
|
||||
|
||||
Reference in New Issue
Block a user