Merge branch 'main' of github.com:appwrite/console into feat-org-creation-refactor

This commit is contained in:
Arman
2024-08-28 14:11:14 +02:00
20 changed files with 1257 additions and 151 deletions
+1
View File
@@ -41,6 +41,7 @@ jobs:
"PUBLIC_CONSOLE_MODE=cloud"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
publish-cloud-stage:
runs-on: ubuntu-latest
steps:
+3
View File
@@ -150,3 +150,6 @@ dist
# SvelteKit build / generate output
.svelte-kit
# Sentry Config File
.sentryclirc
+3
View File
@@ -22,11 +22,14 @@ ARG PUBLIC_CONSOLE_MODE
ARG PUBLIC_APPWRITE_ENDPOINT
ARG PUBLIC_GROWTH_ENDPOINT
ARG PUBLIC_STRIPE_KEY
ARG SENTRY_AUTH_TOKEN
ENV PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
ENV PUBLIC_GROWTH_ENDPOINT=$PUBLIC_GROWTH_ENDPOINT
ENV PUBLIC_CONSOLE_MODE=$PUBLIC_CONSOLE_MODE
ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY
ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
ENV NODE_OPTIONS=--max_old_space_size=8192
RUN pnpm run sync && pnpm run build
+1
View File
@@ -8,6 +8,7 @@ services:
PUBLIC_APPWRITE_ENDPOINT: ${PUBLIC_APPWRITE_ENDPOINT}
PUBLIC_GROWTH_ENDPOINT: ${PUBLIC_GROWTH_ENDPOINT}
PUBLIC_STRIPE_KEY: ${PUBLIC_STRIPE_KEY}
SENTRY_AUTH_TOKEN: ${SENTRY_AUTH_TOKEN}
develop:
watch:
- action: rebuild
+2 -1
View File
@@ -19,10 +19,11 @@
"e2e:ui": "playwright test tests/e2e --ui"
},
"dependencies": {
"@appwrite.io/console": "1.0.0",
"@appwrite.io/console": "1.0.1",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.26.0",
"@stripe/stripe-js": "^3.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.14",
+1056 -5
View File
File diff suppressed because it is too large Load Diff
+23 -10
View File
@@ -1,14 +1,27 @@
import * as Sentry from '@sentry/sveltekit';
import { AppwriteException } from '@appwrite.io/console';
import type { HandleClientError } from '@sveltejs/kit';
import { isCloud, isProd } from '$lib/system';
export const handleError: HandleClientError = async ({ error, message, status }) => {
if (error instanceof AppwriteException) {
status = error.code === 0 ? undefined : error.code;
message = error.message;
Sentry.init({
enabled: isCloud && isProd,
dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.us.sentry.io/4504158071422976',
tracesSampleRate: 1,
replaysSessionSampleRate: 0,
replaysOnErrorSampleRate: 1,
integrations: [Sentry.replayIntegration()]
});
export const handleError: HandleClientError = Sentry.handleErrorWithSentry(
async ({ error, message, status }) => {
if (error instanceof AppwriteException) {
status = error.code === 0 ? undefined : error.code;
message = error.message;
}
return {
message,
status
};
}
return {
message,
status
};
};
);
+14
View File
@@ -0,0 +1,14 @@
import { sequence } from '@sveltejs/kit/hooks';
import { handleErrorWithSentry, sentryHandle } from '@sentry/sveltekit';
import * as Sentry from '@sentry/sveltekit';
import { isCloud, isProd } from '$lib/system';
Sentry.init({
enabled: isCloud && isProd,
dsn: 'https://c7ce178bdedd486480317b72f282fd39@o1063647.ingest.us.sentry.io/4504158071422976',
tracesSampleRate: 1.0
});
export const handle = sequence(sentryHandle());
export const handleError = handleErrorWithSentry();
@@ -1,7 +1,7 @@
<script lang="ts">
import { BillingPlan } from '$lib/constants';
import { formatNum } from '$lib/helpers/string';
import { plansInfo, tierFree, tierPro, tierScale, type Tier } from '$lib/stores/billing';
import { plansInfo, tierFree, tierPro, type Tier } from '$lib/stores/billing';
import { Card, SecondaryTabs, SecondaryTabsItem } from '..';
let selectedTab: Tier = BillingPlan.FREE;
@@ -23,11 +23,11 @@
on:click={() => (selectedTab = BillingPlan.PRO)}>
{tierPro.name}
</SecondaryTabsItem>
<SecondaryTabsItem
<!-- <SecondaryTabsItem
disabled={selectedTab === BillingPlan.SCALE}
on:click={() => (selectedTab = BillingPlan.SCALE)}>
{tierScale.name}
</SecondaryTabsItem>
</SecondaryTabsItem> -->
</SecondaryTabs>
</div>
+5 -1
View File
@@ -45,7 +45,11 @@
};
</script>
<div class="table-with-scroll {classes}" class:u-margin-block-start-16={!noMargin} data-private>
<div
class="table-with-scroll {classes}"
style:border-radius={noStyles ? '0' : ''}
class:u-margin-block-start-16={!noMargin}
data-private>
<div class="table-wrapper" use:hasOverflow={(v) => (isOverflowing = v)}>
<table
class="table"
+2 -1
View File
@@ -79,7 +79,7 @@ const createUploader = () => {
(p) => {
newFile.$id = p.$id;
newFile.progress = p.progress;
newFile.completed = p.progress === 100 ? true : false;
newFile.completed = p.progress === 100;
updateFile(p.$id, newFile);
}
);
@@ -91,6 +91,7 @@ const createUploader = () => {
removeFromQueue: (id: string) => {
update((n) => {
n.files = n.files.filter((f) => f.$id !== id);
n.isOpen = n.files.length !== 0;
return n;
});
},
+2
View File
@@ -23,5 +23,7 @@ export const ENV = {
export const MODE = VARS.CONSOLE_MODE === Mode.CLOUD ? Mode.CLOUD : Mode.SELF_HOSTED;
export const isCloud = MODE === Mode.CLOUD;
export const isSelfHosted = MODE !== Mode.CLOUD;
export const isDev = ENV.DEV;
export const isProd = ENV.PROD;
export const hasStripePublicKey = !!VARS.PUBLIC_STRIPE_KEY;
export const GRACE_PERIOD_OVERRIDE = false;
+4 -4
View File
@@ -32,11 +32,11 @@
{
value: BillingPlan.PRO,
label: `${tierToPlan(BillingPlan.PRO).name} - ${formatCurrency($plansInfo.get(BillingPlan.PRO).price)}/month + add-ons`
},
{
value: BillingPlan.SCALE,
label: `${tierToPlan(BillingPlan.SCALE).name} - ${formatCurrency($plansInfo.get(BillingPlan.SCALE).price)}/month + usage`
}
// {
// value: BillingPlan.SCALE,
// label: `${tierToPlan(BillingPlan.SCALE).name} - ${formatCurrency($plansInfo.get(BillingPlan.SCALE).price)}/month + usage`
// }
]
: [];
@@ -149,9 +149,9 @@
type: 'success',
isHtml: true,
message: `
<b>${$organization.name}</b> has been changed to ${
tierToPlan(billingPlan).name
} plan.`
<b>${$organization.name}</b> will change to ${
tierToPlan(billingPlan).name
} plan at the end of the current billing cycle.`
});
trackEvent(Submit.OrganizationDowngrade, {
@@ -162,7 +162,7 @@
type: 'error',
message: e.message
});
trackError(e, isUpgrade ? Submit.OrganizationUpgrade : Submit.OrganizationDowngrade);
trackError(e, Submit.OrganizationDowngrade);
}
}
@@ -213,22 +213,12 @@
await invalidate(Dependencies.ORGANIZATION);
await goto(`${base}/organization-${org.$id}`);
if (isUpgrade) {
addNotification({
type: 'success',
message: 'Your organization has been upgraded'
});
} else {
addNotification({
type: 'success',
isHtml: true,
message: `
<b>${$organization.name}</b> will change to ${
tierToPlan(billingPlan).name
} plan at the end of the current billing cycle.`
});
}
trackEvent(isUpgrade ? Submit.OrganizationUpgrade : Submit.OrganizationDowngrade, {
addNotification({
type: 'success',
message: 'Your organization has been upgraded'
});
trackEvent(Submit.OrganizationUpgrade, {
plan: tierToPlan(billingPlan)?.name
});
} catch (e) {
@@ -236,7 +226,7 @@
type: 'error',
message: e.message
});
trackError(e, isUpgrade ? Submit.OrganizationUpgrade : Submit.OrganizationDowngrade);
trackError(e, Submit.OrganizationUpgrade);
}
}
@@ -324,6 +314,7 @@
bind:value={feedbackDowngradeReason} />
<InputTextarea
id="comment"
required
label="If you need to elaborate, please do so here"
placeholder="Enter feedback"
bind:value={feedbackMessage} />
@@ -44,30 +44,11 @@
$: hasValidAttributes = $collection?.attributes?.some((attr) => attr.status === 'available');
</script>
<Container>
<div class="heading-grid u-main-justify-between u-cross-center">
<Heading tag="h2" size="5">Documents</Heading>
<div class="u-flex u-main-end is-only-mobile">
<Button
disabled={!(hasAttributes && hasValidAttributes)}
on:click={openWizard}
event="create_document">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create document</span>
</Button>
</div>
<Filters query={data.query} {columns} disabled={!(hasAttributes && hasValidAttributes)} />
<div class="u-flex u-main-end u-gap-16">
<ViewSelector
view={data.view}
{columns}
isCustomCollection
hideView
allowNoColumns
showColsTextMobile />
<div class="is-not-mobile">
{#key $page.params.collection}
<Container>
<div class="heading-grid u-main-justify-between u-cross-center">
<Heading tag="h2" size="5">Documents</Heading>
<div class="u-flex u-main-end is-only-mobile">
<Button
disabled={!(hasAttributes && hasValidAttributes)}
on:click={openWizard}
@@ -76,76 +57,100 @@
<span class="text">Create document</span>
</Button>
</div>
<Filters
query={data.query}
{columns}
disabled={!(hasAttributes && hasValidAttributes)} />
<div class="u-flex u-main-end u-gap-16">
<ViewSelector
view={data.view}
{columns}
isCustomCollection
hideView
allowNoColumns
showColsTextMobile />
<div class="is-not-mobile">
<Button
disabled={!(hasAttributes && hasValidAttributes)}
on:click={openWizard}
event="create_document">
<span class="icon-plus" aria-hidden="true" />
<span class="text">Create document</span>
</Button>
</div>
</div>
</div>
</div>
{#if hasAttributes && hasValidAttributes}
{#if data.documents.total}
<Table {data} />
{#if hasAttributes && hasValidAttributes}
{#if data.documents.total}
<Table {data} />
<PaginationWithLimit
name="Documents"
limit={data.limit}
offset={data.offset}
total={data.documents.total} />
{:else if $hasPageQueries}
<EmptySearch hidePages>
<div class="common-section">
<div class="u-text-center common-section">
<b class="body-text-2 u-bold">Sorry, we couldn't find any documents.</b>
<p>There are no documents that match your filters.</p>
<PaginationWithLimit
name="Documents"
limit={data.limit}
offset={data.offset}
total={data.documents.total} />
{:else if $hasPageQueries}
<EmptySearch hidePages>
<div class="common-section">
<div class="u-text-center common-section">
<b class="body-text-2 u-bold">Sorry, we couldn't find any documents.</b>
<p>There are no documents that match your filters.</p>
</div>
<div class="u-flex common-section u-main-center">
<Button
secondary
on:click={() => {
queries.clearAll();
queries.apply();
}}>
Clear filters
</Button>
</div>
</div>
<div class="u-flex common-section u-main-center">
</EmptySearch>
{:else}
<Empty
single
href="https://appwrite.io/docs/products/databases/documents"
target="document"
on:click={openWizard} />
{/if}
{:else}
<Empty single target="attribute" on:click={() => (showCreateDropdown = true)}>
<div class="u-text-center">
<Heading size="7" tag="h2">Create an attribute to get started.</Heading>
<p class="body-text-2 u-bold u-margin-block-start-4">
Need a hand? Learn more in our documentation.
</p>
</div>
<div class="u-flex u-gap-16 u-main-center">
<Button
external
href="https://appwrite.io/docs/products/databases/collections#attributes"
text
event="empty_documentation"
ariaLabel={`create {target}`}>Documentation</Button>
<CreateAttributeDropdown
bind:showCreateDropdown
bind:showCreate={showCreateAttribute}
bind:selectedOption={selectedAttribute}>
<Button
secondary
event="create_attribute"
on:click={() => {
queries.clearAll();
queries.apply();
showCreateDropdown = !showCreateDropdown;
}}>
Clear filters
Create attribute
</Button>
</div>
</CreateAttributeDropdown>
</div>
</EmptySearch>
{:else}
<Empty
single
href="https://appwrite.io/docs/products/databases/documents"
target="document"
on:click={openWizard} />
</Empty>
{/if}
{:else}
<Empty single target="attribute" on:click={() => (showCreateDropdown = true)}>
<div class="u-text-center">
<Heading size="7" tag="h2">Create an attribute to get started.</Heading>
<p class="body-text-2 u-bold u-margin-block-start-4">
Need a hand? Learn more in our documentation.
</p>
</div>
<div class="u-flex u-gap-16 u-main-center">
<Button
external
href="https://appwrite.io/docs/products/databases/collections#attributes"
text
event="empty_documentation"
ariaLabel={`create {target}`}>Documentation</Button>
<CreateAttributeDropdown
bind:showCreateDropdown
bind:showCreate={showCreateAttribute}
bind:selectedOption={selectedAttribute}>
<Button
secondary
event="create_attribute"
on:click={() => {
showCreateDropdown = !showCreateDropdown;
}}>
Create attribute
</Button>
</CreateAttributeDropdown>
</div>
</Empty>
{/if}
</Container>
</Container>
{/key}
<CreateAttribute bind:showCreate={showCreateAttribute} bind:selectedOption={selectedAttribute} />
@@ -96,7 +96,7 @@
},
{
id: 'size',
title: 'Deployment size',
title: 'Size',
type: 'integer',
show: true,
width: 140,
@@ -8,14 +8,17 @@
</script>
<div class="u-flex u-gap-4 u-cross-center">
<Trim alternativeTrim>
<a href={`http://${domain.rules[0].domain}`} target="_blank">
<a
href={`http://${domain.rules[0].domain}`}
target="_blank"
class="u-flex u-gap-4 u-cross-center">
<Trim alternativeTrim>
<span class="link">
{domain.rules[0].domain}
</span>
</a>
</Trim>
<span class="icon-external-link" aria-hidden="true" />
</Trim>
<span class="icon-external-link" aria-hidden="true" />
</a>
{#if domain.rules.length > 1}
<DropList bind:show={showDropdown} scrollable>
<Pill button on:click={() => (showDropdown = !showDropdown)}>
@@ -4,7 +4,6 @@ import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
import type { PageLoad } from './$types';
import { isCloud } from '$lib/system';
import type { OrganizationUsage } from '$lib/sdk/billing';
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
const { organization } = await parent();
@@ -13,20 +12,20 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) =>
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
let organizationUsage: OrganizationUsage = null;
if (isCloud && organization?.$id) {
organizationUsage = await sdk.forConsole.billing.listUsage(organization.$id);
}
return {
offset,
limit,
search,
files: await sdk.forProject.storage.listFiles(
const [files, organizationUsage] = await Promise.all([
sdk.forProject.storage.listFiles(
params.bucket,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
),
isCloud && organization?.$id ? sdk.forConsole.billing.listUsage(organization.$id) : null
]);
return {
offset,
limit,
search,
files,
organizationUsage
};
};
@@ -28,17 +28,18 @@
});
async function create() {
const fileId = $createFile.id ?? ID.unique();
try {
uploader.uploadFile(
const uploadPromise = uploader.uploadFile(
bucketId,
$createFile.id ?? ID.unique(),
fileId,
$createFile.files[0],
$createFile.permissions
);
createFile.reset();
wizard.hide();
invalidate(Dependencies.FILES);
addNotification({
type: 'success',
@@ -47,7 +48,12 @@
trackEvent(Submit.FileCreate, {
customId: !!$createFile.id
});
await uploadPromise;
invalidate(Dependencies.FILES);
} catch (e) {
uploader.removeFromQueue(fileId);
addNotification({
type: 'error',
message: e.message
+11 -3
View File
@@ -1,16 +1,24 @@
import { defineConfig } from 'vitest/config';
import { sveltekit } from '@sveltejs/kit/vite';
import { sentrySvelteKit } from '@sentry/sveltekit';
const config = defineConfig({
plugins: [sveltekit()],
plugins: [
sentrySvelteKit({
adapter: 'auto',
sourceMapsUploadOptions: {
org: 'appwrite',
project: 'console'
}
}),
sveltekit()
],
optimizeDeps: {
include: ['echarts', 'prismjs']
},
ssr: {
noExternal: [
'@analytics/google-analytics',
'@sentry/browser',
'@sentry-internal/tracing',
'analytics',
'dayjs',
'echarts',