Merge commit '93754b342accbdb6db066c33b2addea7b9e92d67' into feat-v2-regions

This commit is contained in:
Arman
2025-05-02 14:10:55 +02:00
376 changed files with 3533 additions and 2249 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ export function getOrganizationIdFromUrl(pathname: string) {
export function getProjectIdFromUrl(pathname: string) {
// TODO: use base path from svelte here
const regex = /\/console\/project-([^/]+)(\/.*)?/;
const regex = /\/console\/project-(?:[a-z]{2,3}-)?([^/]+)(\/.*)?/;
const match = pathname.match(regex);
if (match) {
+2 -2
View File
@@ -8,12 +8,12 @@ test('upgrade - free tier', async ({ page }) => {
await createFreeProject(page);
await test.step('upgrade project', async () => {
await page.getByRole('link', { name: 'Upgrade', exact: true }).click();
await page.waitForURL('./organization-**/change-plan');
await page.waitForURL(/\/organization-[^/]+\/change-plan/);
await page.locator('input[value="tier-1"]').click();
await page.getByRole('button', { name: 'add' }).first().click();
await enterCreditCard(page);
// skip members
await page.getByRole('button', { name: 'change plan' }).click();
await page.waitForURL('**/console/project-*/overview/platforms');
await page.waitForURL(/\/console\/project-(?:[a-z0-9]+-)?([^/]+)\/overview\/platforms/);
});
});
+3 -3
View File
@@ -13,18 +13,18 @@ export async function createFreeProject(page: Page): Promise<Metadata> {
await page.locator('id=name').fill('test org');
await page.locator('id=plan').selectOption('tier-0');
await page.getByRole('button', { name: 'get started' }).click();
await page.waitForURL('./organization-**');
await page.waitForURL(/\/organization-[^/]+/);
return getOrganizationIdFromUrl(page.url());
});
const projectId = await test.step('create project', async () => {
await page.waitForURL('./organization-**');
await page.waitForURL(/\/organization-[^/]+/);
await page.getByRole('button', { name: 'create project' }).first().click();
await page.locator('id=name').fill('test project');
await page.getByRole('button', { name: 'next' }).click();
await page.locator('label').filter({ hasText: 'Frankfurt' }).click();
await page.getByRole('button', { name: 'create' }).click();
await page.waitForURL('./project-**/overview/platforms');
await page.waitForURL(/\/project-(?:[a-z0-9]+-)?([^/]+)\/overview\/platforms/);
expect(page.url()).toContain('/console/project-');
return getProjectIdFromUrl(page.url());
+4 -4
View File
@@ -32,24 +32,24 @@ export async function createProProject(page: Page): Promise<Metadata> {
await page.locator('id=name').fill('test org');
await page.locator('id=plan').selectOption('tier-1');
await page.getByRole('button', { name: 'get started' }).click();
await page.waitForURL('./create-organization**');
await page.waitForURL(/\/create-organization.*/);
await page.getByRole('button', { name: 'add' }).first().click();
await enterCreditCard(page);
// skip members
await page.getByRole('button', { name: 'create organization' }).click();
await page.waitForURL('./organization-**');
await page.waitForURL(/\/organization-[^/]+/);
return getOrganizationIdFromUrl(page.url());
});
const projectId = await test.step('create project', async () => {
await page.waitForURL('./organization-**');
await page.waitForURL(/\/organization-[^/]+/);
await page.getByRole('button', { name: 'create project' }).first().click();
await page.getByPlaceholder('project name').fill('test project');
await page.getByRole('button', { name: 'next' }).click();
await page.locator('label').filter({ hasText: 'frankfurt' }).click();
await page.getByRole('button', { name: 'create' }).click();
await page.waitForURL('./project-**/overview/platforms');
await page.waitForURL(/\/project-(?:[a-z0-9]+-)?([^/]+)\/overview\/platforms/);
expect(page.url()).toContain('/project-');
return getProjectIdFromUrl(page.url());
+1 -1
View File
@@ -32,7 +32,7 @@
"@stripe/stripe-js": "^3.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.16",
"@ai-sdk/svelte": "^1.1.22",
"@ai-sdk/svelte": "^1.1.24",
"cron-parser": "^4.9.0",
"dayjs": "^1.11.13",
"deep-equal": "^2.2.3",
+19 -19
View File
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@ai-sdk/svelte':
specifier: ^1.1.22
version: 1.1.22(svelte@4.2.19)(zod@3.24.2)
specifier: ^1.1.24
version: 1.1.24(svelte@4.2.19)(zod@3.24.2)
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e
@@ -195,8 +195,8 @@ packages:
'@adobe/css-tools@4.4.2':
resolution: {integrity: sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==}
'@ai-sdk/provider-utils@2.1.11':
resolution: {integrity: sha512-lMnXA5KaRJidzW7gQmlo/SnX6D+AKk5GxHFcQtOaGOSJNmu/qcNZc1rGaO7K5qW52OvCLXtnWudR4cc/FvMpVQ==}
'@ai-sdk/provider-utils@2.1.13':
resolution: {integrity: sha512-kLjqsfOdONr6DGcGEntFYM1niXz1H05vyZNf9OAzK+KKKc64izyP4/q/9HX7W4+6g8hm6BnmKxu8vkr6FSOqDg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
@@ -204,12 +204,12 @@ packages:
zod:
optional: true
'@ai-sdk/provider@1.0.10':
resolution: {integrity: sha512-pco8Zl9U0xwXI+nCLc0woMtxbvjU8hRmGTseAUiPHFLYAAL8trRPCukg69IDeinOvIeo1SmXxAIdWWPZOLb4Cg==}
'@ai-sdk/provider@1.0.11':
resolution: {integrity: sha512-CPyImHGiT3svyfmvPvAFTianZzWFtm0qK82XjwlQIA1C3IQ2iku/PMQXi7aFyrX0TyMh3VTkJPB03tjU2VXVrw==}
engines: {node: '>=18'}
'@ai-sdk/svelte@1.1.22':
resolution: {integrity: sha512-xtJjvEPHD8GDB1iODvsRLKplZ9CVUN/gT4U+nttuEvjV42qoPXKkrWwjTLEdWKnwTnoHuuI8mnQMje6RhFWtpA==}
'@ai-sdk/svelte@1.1.24':
resolution: {integrity: sha512-nlSSd4FirQyM10MQb9vCzF3e/R4id0od0/cKtB1JdtcIvT4ZaJzyltIK3Q72ceOjEbHXSGbucW6gVyExBzHCLQ==}
engines: {node: '>=18'}
peerDependencies:
svelte: ^3.0.0 || ^4.0.0 || ^5.0.0
@@ -217,8 +217,8 @@ packages:
svelte:
optional: true
'@ai-sdk/ui-utils@1.1.17':
resolution: {integrity: sha512-fCnp/wntZGqPf6tiCmhuQoSDLSBhXoI5DU2JX4As96EO870+jliE6ozvYUwYOZC6Ta2OKAjjWPcSP7HeHX0b+g==}
'@ai-sdk/ui-utils@1.1.19':
resolution: {integrity: sha512-rDHy2uxlPMt3jjS9L6mBrsfhEInZ5BVoWevmD13fsAt2s/XWy2OwwKmgmUQkdLlY4mn/eyeYAfDGK8+5CbOAgg==}
engines: {node: '>=18'}
peerDependencies:
zod: ^3.0.0
@@ -3529,32 +3529,32 @@ snapshots:
'@adobe/css-tools@4.4.2': {}
'@ai-sdk/provider-utils@2.1.11(zod@3.24.2)':
'@ai-sdk/provider-utils@2.1.13(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.10
'@ai-sdk/provider': 1.0.11
eventsource-parser: 3.0.0
nanoid: 3.3.9
secure-json-parse: 2.7.0
optionalDependencies:
zod: 3.24.2
'@ai-sdk/provider@1.0.10':
'@ai-sdk/provider@1.0.11':
dependencies:
json-schema: 0.4.0
'@ai-sdk/svelte@1.1.22(svelte@4.2.19)(zod@3.24.2)':
'@ai-sdk/svelte@1.1.24(svelte@4.2.19)(zod@3.24.2)':
dependencies:
'@ai-sdk/provider-utils': 2.1.11(zod@3.24.2)
'@ai-sdk/ui-utils': 1.1.17(zod@3.24.2)
'@ai-sdk/provider-utils': 2.1.13(zod@3.24.2)
'@ai-sdk/ui-utils': 1.1.19(zod@3.24.2)
optionalDependencies:
svelte: 4.2.19
transitivePeerDependencies:
- zod
'@ai-sdk/ui-utils@1.1.17(zod@3.24.2)':
'@ai-sdk/ui-utils@1.1.19(zod@3.24.2)':
dependencies:
'@ai-sdk/provider': 1.0.10
'@ai-sdk/provider-utils': 2.1.11(zod@3.24.2)
'@ai-sdk/provider': 1.0.11
'@ai-sdk/provider-utils': 2.1.13(zod@3.24.2)
zod-to-json-schema: 3.24.3(zod@3.24.2)
optionalDependencies:
zod: 3.24.2
@@ -1,6 +1,6 @@
<script lang="ts">
import { initCreateAttribute } from '$routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/+layout.svelte';
import { attributeOptions } from '$routes/(console)/project-[project]/databases/database-[database]/collection-[collection]/attributes/store';
import { initCreateAttribute } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte';
import { attributeOptions } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/attributes/store';
import Template from './template.svelte';
let search = '';
@@ -1,15 +1,15 @@
<script lang="ts">
import { providers } from '$routes/(console)/project-[project]/messaging/providers/store';
import { providers } from '$routes/(console)/project-[region]-[project]/messaging/providers/store';
import {
messageParams,
providerType,
targetsById
} from '$routes/(console)/project-[project]/messaging/wizard/store';
} from '$routes/(console)/project-[region]-[project]/messaging/wizard/store';
import { MessagingProviderType } from '@appwrite.io/console';
import Template from './template.svelte';
import { wizard } from '$lib/stores/wizard';
import Create from '$routes/(console)/project-[project]/messaging/create.svelte';
import { topicsById } from '$routes/(console)/project-[project]/messaging/store';
import Create from '$routes/(console)/project-[region]-[project]/messaging/create.svelte';
import { topicsById } from '$routes/(console)/project-[region]-[project]/messaging/store';
let search = '';
@@ -2,7 +2,7 @@
import {
Platform,
addPlatform
} from '$routes/(console)/project-[project]/overview/platforms/+page.svelte';
} from '$routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte';
import Template from './template.svelte';
import { IconAndroid, IconApple, IconCode, IconFlutter } from '@appwrite.io/pink-icons-svelte';
+16 -10
View File
@@ -14,12 +14,13 @@ import {
IconPuzzle,
IconSearch
} from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
const getBucketCommand = (bucket: Models.Bucket, projectId: string) => {
const getBucketCommand = (bucket: Models.Bucket, region: string, projectId: string) => {
return {
label: `${bucket.name}`,
callback() {
goto(`${base}/project-${projectId}/storage/bucket-${bucket.$id}`);
goto(`${base}/project-${region}-${projectId}/storage/bucket-${bucket.$id}`);
},
group: 'buckets',
icon: IconFolder
@@ -27,19 +28,24 @@ const getBucketCommand = (bucket: Models.Bucket, projectId: string) => {
};
export const bucketSearcher = (async (query: string) => {
const { buckets } = await sdk.forProject.storage.listBuckets([Query.orderDesc('$createdAt')]);
const $project = get(project);
const region = page.params.region;
const { buckets } = await sdk
.forProject(page.params.region, page.params.project)
.storage.listBuckets([Query.orderDesc('$createdAt')]);
const filtered = buckets.filter((bucket) => bucket.name.includes(query));
if (filtered.length === 1) {
const bucket = filtered[0];
return [
getBucketCommand(bucket, $project.$id),
getBucketCommand(bucket, region, $project.$id),
{
label: 'Find files',
async callback() {
await goto(`${base}/project-${$project.$id}/storage/bucket-${bucket.$id}`);
await goto(
`${base}/project-${$project.region}-${$project.$id}/storage/bucket-${bucket.$id}`
);
addSubPanel(FilesPanel);
},
group: 'buckets',
@@ -51,7 +57,7 @@ export const bucketSearcher = (async (query: string) => {
label: 'Permissions',
async callback() {
await goto(
`${base}/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#permissions`
`${base}/project-${$project.region}-${$project.$id}/storage/bucket-${bucket.$id}/settings#permissions`
);
scrollBy({ top: -100 });
},
@@ -63,7 +69,7 @@ export const bucketSearcher = (async (query: string) => {
label: 'Extensions',
async callback() {
await goto(
`${base}/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#extensions`
`${base}/project-${$project.region}-${$project.$id}/storage/bucket-${bucket.$id}/settings#extensions`
);
},
group: 'buckets',
@@ -74,7 +80,7 @@ export const bucketSearcher = (async (query: string) => {
label: 'File Security',
async callback() {
await goto(
`${base}/project-${$project.$id}/storage/bucket-${bucket.$id}/settings#file-security`
`${base}/project-${$project.region}-${$project.$id}/storage/bucket-${bucket.$id}/settings#file-security`
);
scrollBy({ top: -100 });
},
@@ -85,5 +91,5 @@ export const bucketSearcher = (async (query: string) => {
];
}
return filtered.map((bucket) => getBucketCommand(bucket, $project.$id));
return filtered.map((bucket) => getBucketCommand(bucket, $project.region, $project.$id));
}) satisfies Searcher;
@@ -1,16 +1,17 @@
import { goto } from '$app/navigation';
import { database } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/store';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { base } from '$app/paths';
import { page } from '$app/state';
export const collectionsSearcher = (async (query: string) => {
const databaseId = get(database).$id;
const { collections } = await sdk.forProject.databases.listCollections(databaseId);
const { collections } = await sdk
.forProject(page.params.region, page.params.project)
.databases.listCollections(databaseId);
const projectId = get(project).$id;
return collections
.filter((col) => col.name.toLowerCase().includes(query.toLowerCase()))
.map(
@@ -20,7 +21,7 @@ export const collectionsSearcher = (async (query: string) => {
label: col.name,
callback: () => {
goto(
`${base}/project-${projectId}/databases/database-${databaseId}/collection-${col.$id}`
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/collection-${col.$id}`
);
}
}) as const
+7 -2
View File
@@ -5,9 +5,12 @@ import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { base } from '$app/paths';
import { IconDatabase } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
export const dbSearcher = (async (query: string) => {
const { databases } = await sdk.forProject.databases.list();
const { databases } = await sdk
.forProject(page.params.region, page.params.project)
.databases.list();
return databases
.filter((db) => db.name.toLowerCase().includes(query.toLowerCase()))
@@ -17,7 +20,9 @@ export const dbSearcher = (async (query: string) => {
group: 'databases',
label: db.name,
callback: () => {
goto(`${base}/project-${get(project).$id}/databases/database-${db.$id}`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${db.$id}`
);
},
icon: IconDatabase
}) as const
+7 -6
View File
@@ -7,21 +7,22 @@ import { goto } from '$app/navigation';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { base } from '$app/paths';
import { IconDocument } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
export const fileSearcher = (async (query: string) => {
const $bucket = get(bucket);
const $project = get(project);
const { files } = await sdk.forProject.storage.listFiles(
$bucket.$id,
[Query.orderDesc('')],
query || undefined
);
const { files } = await sdk
.forProject(page.params.region, page.params.project)
.storage.listFiles($bucket.$id, [Query.orderDesc('')], query || undefined);
return files.map((file) => ({
label: file.name,
callback: () => {
goto(`${base}/project-${$project.$id}/storage/bucket-${$bucket.$id}/file-${file.$id}`);
goto(
`${base}/project-${$project.region}-${$project.$id}/storage/bucket-${$bucket.$id}/file-${file.$id}`
);
},
icon: IconDocument,
group: 'files'
+23 -11
View File
@@ -9,11 +9,11 @@ import { showCreateDeployment } from '$routes/(console)/project-[region]-[projec
import { base } from '$app/paths';
import { IconLightningBolt, IconPlus } from '@appwrite.io/pink-icons-svelte';
const getFunctionCommand = (fn: Models.Function, projectId: string) => {
const getFunctionCommand = (fn: Models.Function, region: string, projectId: string) => {
return {
label: fn.name,
callback: () => {
goto(`${base}/project-${projectId}/functions/function-${fn.$id}`);
goto(`${base}/project-${region}-${projectId}/functions/function-${fn.$id}`);
},
group: 'functions',
icon: IconLightningBolt
@@ -21,23 +21,27 @@ const getFunctionCommand = (fn: Models.Function, projectId: string) => {
};
export const functionsSearcher = (async (query: string) => {
const { functions } = await sdk.forProject.functions.list();
const $page = get(page);
const projectId = get(project).$id;
const { functions } = await sdk
.forProject($page.params.region, $page.params.project)
.functions.list();
const filtered = functions.filter((fn) => fn.name.toLowerCase().includes(query.toLowerCase()));
if (filtered.length === 1) {
const func = filtered[0];
return [
getFunctionCommand(func, projectId),
getFunctionCommand(func, $page.params.region, projectId),
{
label: 'Create deployment',
nested: true,
async callback() {
const $page = get(page);
if (!$page.url.pathname.endsWith(func.$id)) {
await goto(`${base}/project-${projectId}/functions/function-${func.$id}`);
await goto(
`${base}/project-${$page.params.region}-${projectId}/functions/function-${func.$id}`
);
}
showCreateDeployment.set(true);
},
@@ -48,7 +52,9 @@ export const functionsSearcher = (async (query: string) => {
label: 'Go to deployments',
nested: true,
callback() {
goto(`${base}/project-${projectId}/functions/function-${func.$id}`);
goto(
`${base}/project-${$page.params.region}-${projectId}/functions/function-${func.$id}`
);
},
group: 'functions'
},
@@ -56,7 +62,9 @@ export const functionsSearcher = (async (query: string) => {
label: 'Go to usage',
nested: true,
callback() {
goto(`${base}/project-${projectId}/functions/function-${func.$id}/usage`);
goto(
`${base}/project-${$page.params.region}-${projectId}/functions/function-${func.$id}/usage`
);
},
group: 'functions'
},
@@ -64,7 +72,9 @@ export const functionsSearcher = (async (query: string) => {
label: 'Go to executions',
nested: true,
callback() {
goto(`${base}/project-${projectId}/functions/function-${func.$id}/executions`);
goto(
`${base}/project-${$page.params.region}-${projectId}/functions/function-${func.$id}/executions`
);
},
group: 'functions'
},
@@ -72,12 +82,14 @@ export const functionsSearcher = (async (query: string) => {
label: 'Go to settings',
nested: true,
callback() {
goto(`${base}/project-${projectId}/functions/function-${func.$id}/settings`);
goto(
`${base}/project-${$page.params.region}-${projectId}/functions/function-${func.$id}/settings`
);
},
group: 'functions'
}
];
}
return filtered.map((fn) => getFunctionCommand(fn, projectId));
return filtered.map((fn) => getFunctionCommand(fn, $page.params.region, projectId));
}) satisfies Searcher;
+7 -6
View File
@@ -1,11 +1,10 @@
import { goto } from '$app/navigation';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import { type Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { MessagingProviderType, type Models } from '@appwrite.io/console';
import { base } from '$app/paths';
import { IconAnnotation, IconDeviceMobile, IconMail } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
const getLabel = (message: Models.Message) => {
switch (message.providerType) {
@@ -34,9 +33,9 @@ const getIcon = (message: Models.Message) => {
};
export const messagesSearcher = (async (query: string) => {
const { messages } = await sdk.forProject.messaging.listMessages([], query || undefined);
const projectId = get(project).$id;
const { messages } = await sdk
.forProject(page.params.region, page.params.project)
.messaging.listMessages([], query || undefined);
return messages
.filter((message) => getLabel(message).toLowerCase().includes(query.toLowerCase()))
@@ -46,7 +45,9 @@ export const messagesSearcher = (async (query: string) => {
group: 'messages',
label: getLabel(message),
callback: () => {
goto(`${base}/project-${projectId}/messaging/message-${message.$id}`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/messaging/message-${message.$id}`
);
},
icon: getIcon(message)
}) as const
+1 -1
View File
@@ -18,7 +18,7 @@ export const projectsSearcher = (async (query: string) => {
return {
label: project.name,
callback: () => {
goto(`${base}/project-${project.$id}`);
goto(`${base}/project-${project.region}-${project.$id}`);
},
group: 'projects'
} as const;
+6 -7
View File
@@ -1,10 +1,9 @@
import { goto } from '$app/navigation';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { getProviderDisplayNameAndIcon } from '$routes/(console)/project-[project]/messaging/provider.svelte';
import { getProviderDisplayNameAndIcon } from '$routes/(console)/project-[region]-[project]/messaging/provider.svelte';
import { base } from '$app/paths';
import { page } from '$app/state';
const getIcon = (provider: string) => {
const { icon } = getProviderDisplayNameAndIcon(provider);
@@ -12,9 +11,9 @@ const getIcon = (provider: string) => {
};
export const providersSearcher = (async (query: string) => {
const { providers } = await sdk.forProject.messaging.listProviders([], query || undefined);
const projectId = get(project).$id;
const { providers } = await sdk
.forProject(page.params.region, page.params.project)
.messaging.listProviders([], query || undefined);
return providers
.filter((provider) => provider.name.toLowerCase().includes(query.toLowerCase()))
@@ -25,7 +24,7 @@ export const providersSearcher = (async (query: string) => {
label: provider.name,
callback: () => {
goto(
`${base}/project-${projectId}/messaging/providers/provider-${provider.$id}`
`${base}/project-${page.params.region}-${page.params.project}/messaging/providers/provider-${provider.$id}`
);
},
image: getIcon(provider.provider)
+14 -10
View File
@@ -1,33 +1,35 @@
import { goto } from '$app/navigation';
import { sdk } from '$lib/stores/sdk';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import type { Command, Searcher } from '../commands';
import type { Models } from '@appwrite.io/console';
import { base } from '$app/paths';
import { IconUserCircle } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
const getTeamCommand = (team: Models.Team<Models.Preferences>, projectId: string) =>
const getTeamCommand = (team: Models.Team<Models.Preferences>, region: string, projectId: string) =>
({
label: team.name,
callback: () => {
goto(`${base}/project-${projectId}/auth/teams/team-${team.$id}`);
goto(`${base}/project-${region}-${projectId}/auth/teams/team-${team.$id}`);
},
group: 'teams',
icon: IconUserCircle
}) satisfies Command;
export const teamSearcher = (async (query: string) => {
const { teams } = await sdk.forProject.teams.list([], query);
const projectId = get(project).$id;
const { teams } = await sdk
.forProject(page.params.region, page.params.project)
.teams.list([], query);
if (teams.length === 1) {
return [
getTeamCommand(teams[0], projectId),
getTeamCommand(teams[0], page.params.region, page.params.project),
{
label: 'Go to members',
callback: () => {
goto(`${base}/project-${projectId}/auth/teams/team-${teams[0].$id}/members`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${teams[0].$id}/members`
);
},
group: 'teams',
nested: true
@@ -36,12 +38,14 @@ export const teamSearcher = (async (query: string) => {
{
label: 'Go to activity',
callback: () => {
goto(`${base}/project-${projectId}/auth/teams/team-${teams[0].$id}/activity`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${teams[0].$id}/activity`
);
},
group: 'teams',
nested: true
}
];
}
return teams.map((team) => getTeamCommand(team, projectId));
return teams.map((team) => getTeamCommand(team, page.params.region, page.params.project));
}) satisfies Searcher;
+7 -6
View File
@@ -1,15 +1,14 @@
import { goto } from '$app/navigation';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import type { Searcher } from '../commands';
import { sdk } from '$lib/stores/sdk';
import { base } from '$app/paths';
import { IconChevronRight } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
export const topicsSearcher = (async (query: string) => {
const { topics } = await sdk.forProject.messaging.listTopics([], query || undefined);
const projectId = get(project).$id;
const { topics } = await sdk
.forProject(page.params.region, page.params.project)
.messaging.listTopics([], query || undefined);
return topics
.filter((topic) => topic.name.toLowerCase().includes(query.toLowerCase()))
@@ -19,7 +18,9 @@ export const topicsSearcher = (async (query: string) => {
group: 'topics',
label: topic.name,
callback: () => {
goto(`${base}/project-${projectId}/messaging/topics/topic-${topic.$id}`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/messaging/topics/topic-${topic.$id}`
);
},
icon: IconChevronRight // TODO: @itznotabug - 'send' no replacement yet.
}) as const
+18 -12
View File
@@ -1,30 +1,30 @@
import { goto } from '$app/navigation';
import { sdk } from '$lib/stores/sdk';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { get } from 'svelte/store';
import type { Command, Searcher } from '../commands';
import type { Models } from '@appwrite.io/console';
import { promptDeleteUser } from '$routes/(console)/project-[project]/auth/user-[user]/dangerZone.svelte';
import { promptDeleteUser } from '$routes/(console)/project-[region]-[project]/auth/user-[user]/dangerZone.svelte';
import { base } from '$app/paths';
import { IconTrash, IconUserCircle } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
const getUserCommand = (user: Models.User<Models.Preferences>, projectId: string) =>
const getUserCommand = (user: Models.User<Models.Preferences>, region: string, projectId: string) =>
({
label: user.name,
callback: () => {
goto(`${base}/project-${projectId}/auth/user-${user.$id}`);
goto(`${base}/project-${region}-${projectId}/auth/user-${user.$id}`);
},
group: 'users',
icon: IconUserCircle
}) satisfies Command;
export const userSearcher = (async (query: string) => {
const { users } = await sdk.forProject.users.list([], query || undefined);
const projectId = get(project).$id;
const { users } = await sdk
.forProject(page.params.region, page.params.project)
.users.list([], query || undefined);
if (users.length === 1) {
return [
getUserCommand(users[0], projectId),
getUserCommand(users[0], page.params.region, page.params.project),
{
label: 'Delete user',
callback: () => {
@@ -37,7 +37,9 @@ export const userSearcher = (async (query: string) => {
{
label: 'Go to activity',
callback: () => {
goto(`${base}/project-${projectId}/auth/user-${users[0].$id}/activity`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/user-${users[0].$id}/activity`
);
},
group: 'users',
nested: true
@@ -45,7 +47,9 @@ export const userSearcher = (async (query: string) => {
{
label: 'Go to sessions',
callback: () => {
goto(`${base}/project-${projectId}/auth/user-${users[0].$id}/sessions`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/user-${users[0].$id}/sessions`
);
},
group: 'users',
nested: true
@@ -53,12 +57,14 @@ export const userSearcher = (async (query: string) => {
{
label: 'Go to memberships',
callback: () => {
goto(`${base}/project-${projectId}/auth/user-${users[0].$id}/memberships`);
goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/user-${users[0].$id}/memberships`
);
},
group: 'users',
nested: true
}
];
}
return users.map((user) => getUserCommand(user, projectId));
return users.map((user) => getUserCommand(user, page.params.region, page.params.project));
}) satisfies Searcher;
+16 -11
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { sdk } from '$lib/stores/sdk';
import { realtime } from '$lib/stores/sdk';
import { type Payload } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { isCloud, isSelfHosted } from '$lib/system';
@@ -34,6 +34,7 @@
function showRestoreNotification(newDatabaseId: string, newDatabaseName: string) {
if (newDatabaseId && newDatabaseName && lastDatabaseRestorationId !== newDatabaseId) {
const region = page.params.region;
const project = page.params.project;
lastDatabaseRestorationId = newDatabaseId;
@@ -45,7 +46,9 @@
{
name: 'View restored data',
method: () => {
goto(`${base}/project-${project}/databases/database-${newDatabaseId}`);
goto(
`${base}/project-${region}-${project}/databases/database-${newDatabaseId}`
);
}
}
]
@@ -123,16 +126,18 @@
// fast path: don't subscribe if org is on a free plan or is self-hosted.
if (isSelfHosted || (isCloud && $organization.billingPlan === BillingPlan.FREE)) return;
return sdk.forConsole.client.subscribe('console', (response) => {
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
return realtime
.forProject(page.params.region, page.params.project)
.subscribe('console', (response) => {
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
if (
response.events.includes('archives.*') ||
response.events.includes('restorations.*')
) {
updateOrAddItem(response.payload);
}
});
if (
response.events.includes('archives.*') ||
response.events.includes('restorations.*')
) {
updateOrAddItem(response.payload);
}
});
});
</script>
+2 -1
View File
@@ -9,8 +9,9 @@
export let event: string = null;
export let eventContext = 'click_id_tag';
export let tooltipDisabled = false;
export let copyText: string = 'Click to copy';
let content = 'Click to copy';
let content = copyText;
async function handleClick() {
const success = await copy(value);
@@ -9,7 +9,7 @@
function getCreditCardImage(brand: string, width = 46, height = 32) {
if (!isValueOfStringEnum(CreditCard, brand)) return '';
return sdk.forConsole.avatars.getCreditCard(brand, width, height).toString();
return sdk.forConsole.avatars.getCreditCard(brand, width, height);
}
</script>
+19 -12
View File
@@ -49,20 +49,21 @@
function getPreview(bucketId: string, fileId: string, size: number = 64) {
return (
sdk.forProject.storage.getFilePreview(bucketId, fileId, size, size).toString() +
'&mode=admin'
sdk
.forProject(page.params.region, page.params.project)
.storage.getFilePreview(bucketId, fileId, size, size)
.toString() + '&mode=admin'
);
}
async function uploadFile() {
try {
uploading = true;
const file = await sdk.forProject.storage.createFile(
selectedBucket,
ID.unique(),
fileSelector.files[0],
[Permission.read(Role.any())]
);
const file = await sdk
.forProject(page.params.region, page.params.project)
.storage.createFile(selectedBucket, ID.unique(), fileSelector.files[0], [
Permission.read(Role.any())
]);
search.set($search === null ? '' : null);
selectFile(file);
} catch (e) {
@@ -110,7 +111,9 @@
let buckets: Promise<Models.BucketList> = loadBuckets();
async function loadBuckets() {
const response = await sdk.forProject.storage.listBuckets();
const response = await sdk
.forProject(page.params.region, page.params.project)
.storage.listBuckets();
const bucket = response.buckets[0] ?? null;
if (bucket) {
currentBucket = bucket;
@@ -122,8 +125,9 @@
$: files =
currentBucket &&
sdk.forProject.storage
.listFiles(
sdk
.forProject(page.params.region, page.params.project)
.storage.listFiles(
currentBucket.$id,
[Query.startsWith('mimeType', mimeTypeQuery), Query.orderDesc('$createdAt')],
$search || undefined
@@ -218,6 +222,9 @@
<span class="icon-upload" aria-hidden="true"></span>
<span>Upload</span>
{/if}
======= external href={`${base}/project-${page.params.region}-${page.params.project}/storage`}>
Create bucket >>>>>>>
93754b342accbdb6db066c33b2addea7b9e92d67
</Button>
</Layout.Stack>
</Layout.Stack>
@@ -355,7 +362,7 @@
slot="actions"
secondary
external
href={`${base}/project-${page.params.project}/storage`}>
href={`${base}/project-${page.params.region}-${page.params.project}/storage`}>
Create bucket
</Button>
</Empty>
+11 -9
View File
@@ -1,12 +1,5 @@
<script lang="ts">
import { Icon, Tag } from '@appwrite.io/pink-svelte';
import { Copy } from '.';
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
export let value: string;
export let event: string = null;
function truncateText(node: HTMLElement) {
<script context="module" lang="ts">
export function truncateText(node: HTMLElement) {
const MAX_TRIES = 100;
let originalText = node.textContent;
function checkOverflow() {
@@ -48,6 +41,15 @@
}
</script>
<script lang="ts">
import { Icon, Tag } from '@appwrite.io/pink-svelte';
import { Copy } from '.';
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
export let value: string;
export let event: string = null;
</script>
<Copy {value} {event}>
<Tag size="xs" variant="code">
<Icon icon={IconDuplicate} size="s" slot="start" />
+1
View File
@@ -82,3 +82,4 @@ export { default as BottomSheet } from './bottom-sheet/index';
export { default as Confirm } from './confirm.svelte';
export { default as UsageCard } from './usageCard.svelte';
export { default as ViewToggle } from './viewToggle.svelte';
export { default as RegionEndpoint } from './regionEndpoint.svelte';
+1 -1
View File
@@ -21,7 +21,7 @@
const url = new URL(pageStore.url);
const previousLimit = Number(url.searchParams.get('limit'));
url.searchParams.set('limit', limit.toString());
preferences.setLimit(limit);
preferences.setLimit($pageStore.params.project, $pageStore.route, limit);
if (url.searchParams.has('page')) {
const page = Number(url.searchParams.get('page'));
+11 -8
View File
@@ -1,7 +1,8 @@
<script lang="ts" context="module">
import { page } from '$app/stores';
import { parseIfString } from '$lib/helpers/object';
import { getProjectId } from '$lib/helpers/project';
import { sdk } from '$lib/stores/sdk';
import { realtime } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { writable } from 'svelte/store';
@@ -49,13 +50,15 @@
})();
onMount(() => {
return sdk.forConsole.client.subscribe<Models.Migration>(['console'], async (response) => {
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
if (response.events.includes('migrations.*')) {
if (response.payload.source === 'Backup') return;
migration = response.payload;
}
});
return realtime
.forProject($page.params.region, $page.params.project)
.subscribe<Models.Migration>(['console'], async (response) => {
if (!response.channels.includes(`projects.${getProjectId()}`)) return;
if (response.events.includes('migrations.*')) {
if (response.payload.source === 'Backup') return;
migration = response.payload;
}
});
});
</script>
+2 -1
View File
@@ -6,6 +6,7 @@
export let show = false;
export let size: 'small' | 'big' | 'huge' = null;
export let closable = true;
export let closeByEscape = true;
export let headerDivider = true;
export let style = '';
@@ -48,7 +49,7 @@
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
if (event.key === 'Escape' && closeByEscape) {
event.preventDefault();
trackEvent(Click.ModalCloseClick, {
from: 'escape'
+10 -4
View File
@@ -27,10 +27,16 @@
const role = permission.split(':')[0];
const id = permission.split(':')[1].split('/')[0];
if (role === 'user') {
return await sdk.forProject.users.get(id);
const user = await sdk
.forProject(page.params.region, page.params.project)
.users.get(id);
return user;
}
if (role === 'team') {
return await sdk.forProject.teams.get(id);
const team = await sdk
.forProject(page.params.region, page.params.project)
.teams.get(id);
return team;
}
}
</script>
@@ -82,7 +88,7 @@
{/if}
<div>
<Button.Anchor
href={`${base}/project-${page.params.project}/auth/user-${data?.$id}`}
href={`${base}/project-${page.params.region}-${page.params.project}/auth/user-${data?.$id}`}
size="xs"
target="_blank"
variant="secondary">
@@ -94,7 +100,7 @@
<Typography.Text>Members: {data?.total}</Typography.Text>
<div>
<Button.Anchor
href={`${base}/project-${page.params.project}/auth/teams/team-${data?.$id}`}
href={`${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${data?.$id}`}
size="s"
target="_blank"
variant="secondary">
+4 -4
View File
@@ -15,6 +15,7 @@
Table,
Typography
} from '@appwrite.io/pink-svelte';
import { page } from '$app/state';
export let show: boolean;
export let groups: Writable<Map<string, Permission>>;
@@ -40,10 +41,9 @@
async function request() {
if (!show) return;
results = await sdk.forProject.teams.list(
[Query.limit(5), Query.offset(offset)],
search || undefined
);
results = await sdk
.forProject(page.params.region, page.params.project)
.teams.list([Query.limit(5), Query.offset(offset)], search || undefined);
}
function onSelection(role: string) {
+4 -4
View File
@@ -18,6 +18,7 @@
Typography
} from '@appwrite.io/pink-svelte';
import { IconAnonymous, IconMinusSm } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
export let show: boolean;
export let groups: Writable<Map<string, Permission>>;
@@ -43,10 +44,9 @@
async function request() {
if (!show) return;
results = await sdk.forProject.users.list(
[Query.limit(5), Query.offset(offset)],
search || undefined
);
results = await sdk
.forProject(page.params.region, page.params.project)
.users.list([Query.limit(5), Query.offset(offset)], search || undefined);
}
function onSelection(role: string) {
+40
View File
@@ -0,0 +1,40 @@
<script lang="ts">
import { Copy } from '.';
import { sdk } from '$lib/stores/sdk';
import { Flag } from '@appwrite.io/console';
import { truncateText } from '$lib/components/id.svelte';
import { isValueOfStringEnum } from '$lib/helpers/types';
import { getProjectEndpoint } from '$lib/helpers/project';
import { projectRegion } from '$routes/(console)/project-[region]-[project]/store';
$: flagSrc =
$projectRegion && isValueOfStringEnum(Flag, $projectRegion.flag)
? sdk.forConsole.avatars.getFlag($projectRegion.flag, 30, 20, 100)
: '';
</script>
<Copy value={getProjectEndpoint()} appendTo="parent" copyText="Copy endpoint">
<div
class="flex u-gap-8 u-cross-center interactive-text-output is-buttons-on-top u-text-center"
style:min-inline-size="0"
style:display="inline-flex">
<span
style:white-space="nowrap"
class="text u-line-height-1-5"
style:overflow="hidden"
style:word-break="break-all"
use:truncateText
style:font-family="unset">
{$projectRegion?.name}
</span>
{#if flagSrc}
<img
style="border-radius: 2.5px"
src={flagSrc}
alt={$projectRegion?.name}
width={16}
height={12} />
{/if}
</div>
</Copy>
+8
View File
@@ -3,6 +3,14 @@ export const CARD_LIMIT = 6; // default card limit
export const INTERVAL = 5 * 60000; // default interval to check for feedback
export const NEW_DEV_PRO_UPGRADE_COUPON = 'appw50';
export const REGION_FRA = 'fra';
export const REGION_SYD = 'syd';
export const REGION_NYC = 'nyc';
export const SUBDOMAIN_FRA = 'fra.';
export const SUBDOMAIN_SYD = 'syd.';
export const SUBDOMAIN_NYC = 'nyc.';
export enum Dependencies {
FACTORS = 'dependency:factors',
IDENTITIES = 'dependency:identities',
+1
View File
@@ -2,6 +2,7 @@
import { isValueOfStringEnum } from '$lib/helpers/types';
import { sdk } from '$lib/stores/sdk';
import { Flag } from '@appwrite.io/console';
export let flag: string;
export let name: string = flag;
export let width = 40;
+11 -4
View File
@@ -9,8 +9,15 @@ export function getPage(url: URL): number {
return Number(url.searchParams.get('page'));
}
export function getLimit(url: URL, route: Page['route'], fallback: number): number {
return Number(url.searchParams.get('limit') ?? preferences.get(route).limit ?? fallback);
export function getLimit(
projectId: string,
url: URL,
route: Page['route'],
fallback: number
): number {
return Number(
url.searchParams.get('limit') ?? preferences.get(projectId, route).limit ?? fallback
);
}
export enum View {
@@ -28,8 +35,8 @@ export function getView(url: URL, route: Page['route'], fallback: View, overwrit
: (overwrite ?? View.Table ?? fallback);
}
export function getColumns(route: Page['route'], fallback: string[]): string[] {
return preferences.get(route).columns ?? fallback;
export function getColumns(projectId: string, route: Page['route'], fallback: string[]): string[] {
return preferences.get(projectId, route).columns ?? fallback;
}
export function getSearch(url: URL): string | undefined {
+40 -2
View File
@@ -1,6 +1,44 @@
export function getProjectId() {
import { page } from '$app/stores';
import { get } from 'svelte/store';
import { sdk } from '$lib/stores/sdk';
import { projectRegion } from '$routes/(console)/project-[region]-[project]/store';
/**
* Returns the current project ID.
*
* The function first checks for a `project` parameter in the Svelte `$page` store.
* If not found, it extracts the project ID from the pathname.
*
* Supports:
* - Legacy structure: `/project-{projectID}/`
* - Multi-region structure: `/project-{region}-{projectID}/`
*
* Example:
* - `/project-console/` `console`
* - `/project-fra-console/` `console`
* - `/project-nyc-console/` `console`
*/
export function getProjectId(): string | null {
// safety check!
const projectFromParams = get(page)?.params?.project;
if (projectFromParams) {
return projectFromParams;
}
const pathname = window.location.pathname + '/';
const projectMatch = pathname.match(/\/project-(.*?)\//);
const projectMatch = pathname.match(/\/project-(?:[a-z]{2,3}-)?([^/]+)/);
return projectMatch?.[1] || null;
}
/**
* Returns the correct API endpoint for the project based on the current project region.
*
* @returns {string} The project-specific API endpoint.
*/
export function getProjectEndpoint(): string {
const currentProjectRegion = get(projectRegion);
const { protocol, hostname, href } = new URL(sdk.forConsole.client.config.endpoint);
return currentProjectRegion ? `${protocol}//${currentProjectRegion.$id}.${hostname}/v1` : href;
}
+4
View File
@@ -34,6 +34,10 @@
</Layout.Stack>
<style>
header {
margin-block-end: 1.25rem;
}
.hide-divider {
padding-block-end: 0;
border-block-end: none;
+5 -4
View File
@@ -7,15 +7,16 @@
import Step1 from './wizard/step1.svelte';
import Step2 from './wizard/step2.svelte';
import { onMount } from 'svelte';
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { page } from '$app/stores';
import { realtime } from '$lib/stores/sdk';
onMount(() => {
domain.set({ $id: '', domain: '' });
return sdk.forConsole.client.subscribe<Models.ProxyRule>('console', (data) =>
domain.set(data.payload)
);
return realtime
.forProject($page.params.region, $page.params.region)
.subscribe<Models.ProxyRule>('console', (data) => domain.set(data.payload));
});
async function onFinish() {
+4 -1
View File
@@ -6,6 +6,7 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import type { Dependencies } from '$lib/constants';
import Confirm from '$lib/components/confirm.svelte';
import { page } from '$app/state';
export let showDelete = false;
export let selectedDomain: Models.ProxyRule;
@@ -27,7 +28,9 @@
async function deleteDomain() {
try {
await sdk.forProject.proxy.deleteRule(selectedDomain.$id);
await sdk
.forProject(page.params.region, page.params.project)
.proxy.deleteRule(selectedDomain.$id);
await invalidate(dependency);
showDelete = false;
addNotification({
@@ -3,11 +3,13 @@
import { Button } from '$lib/elements/forms';
import { Table } from '@appwrite.io/pink-svelte';
import { domain } from './store';
import { getProjectEndpoint } from '$lib/helpers/project';
const target = window?.location.hostname ?? '';
$: parts = $domain.domain.split('.');
$: registerable = [parts[parts.length - 2], parts[parts.length - 1]].join('.');
$: cnameValue = $domain.domain.replace('.' + registerable, '');
$: target = new URL(getProjectEndpoint()).hostname;
</script>
<Table.Root
+4 -1
View File
@@ -11,6 +11,7 @@
import type { Models } from '@appwrite.io/console';
import { Card, Icon, Layout, Link, Typography } from '@appwrite.io/pink-svelte';
import { IconExclamationCircle } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
let retrying = false;
export let showTitle = true;
@@ -21,7 +22,9 @@
async function retry() {
try {
retrying = true;
domain = await sdk.forProject.proxy.updateRuleVerification(domain.$id);
domain = await sdk
.forProject(page.params.region, page.params.project)
.proxy.updateRuleVerification(domain.$id);
invalidate(Dependencies.FUNCTION_DOMAINS);
addNotification({
message:
+7 -3
View File
@@ -8,6 +8,7 @@
import { domain } from './store';
import { consoleVariables } from '$routes/(console)/store';
import { Alert } from '@appwrite.io/pink-svelte';
import { page } from '$app/state';
let error = null;
const isDomainsEnabled = $consoleVariables?._APP_DOMAIN_ENABLED === true;
@@ -15,11 +16,14 @@
async function createDomain() {
try {
if ($domain.$id) {
await sdk.forProject.proxy.deleteRule($domain.$id);
await sdk
.forProject(page.params.region, page.params.project)
.proxy.deleteRule($domain.$id);
}
// TODO: move to new api methods
$domain = await sdk.forProject.proxy.createFunctionRule($domain.domain, $func.$id);
$domain = await sdk
.forProject(page.params.region, page.params.project)
.proxy.createFunctionRule($domain.domain, $func.$id);
trackEvent(Submit.DomainCreate);
} catch (e) {
+4 -2
View File
@@ -1277,9 +1277,11 @@ export class Billing {
);
}
async listRegions(): Promise<RegionList> {
async listRegions(teamId: string): Promise<RegionList> {
const path = `/console/regions`;
const params = {};
const params = {
teamId
};
const uri = new URL(this.client.config.endpoint + path);
return await this.client.call(
'GET',
+1 -1
View File
@@ -325,7 +325,7 @@ export async function checkForUsageLimit(org: Organization) {
const members = org.total;
const plan = get(plansInfo)?.get(org.billingPlan);
const membersOverflow =
members - 1 > plan.addons.seats.limit ? members - (plan.addons.seats.limit || members) : 0;
members > plan.addons.seats.limit ? members - (plan.addons.seats.limit || members) : 0;
if (resources.some((r) => r.value >= 100) || membersOverflow > 0) {
readOnly.set(true);
+8 -5
View File
@@ -14,7 +14,11 @@ export const backupsBannerId = 'banner:databaseBackups';
export const showPolicyAlert = writable<boolean>(false);
export async function checkForDatabaseBackupPolicies(database: Models.Database) {
export async function checkForDatabaseBackupPolicies(
region: string,
projectId: string,
database: Models.Database
) {
// fast path: return if user dismissed the banner
if (!shouldShowNotification(backupsBannerId)) return;
@@ -23,10 +27,9 @@ export async function checkForDatabaseBackupPolicies(database: Models.Database)
if (isCloud && backupsEnabled) {
try {
const policies = await sdk.forProject.backups.listPolicies([
Query.limit(1),
Query.equal('resourceId', database.$id)
]);
const policies = await sdk
.forProject(region, projectId)
.backups.listPolicies([Query.limit(1), Query.equal('resourceId', database.$id)]);
total = policies.total;
} catch (e) {
+9 -9
View File
@@ -1,13 +1,13 @@
import type { Component } from 'svelte';
import Apple from '$routes/(console)/project-[project]/auth/(providers)/appleOAuth.svelte';
import Auth0 from '$routes/(console)/project-[project]/auth/(providers)/auth0OAuth.svelte';
import Authentik from '$routes/(console)/project-[project]/auth/(providers)/authentikOAuth.svelte';
import GitLab from '$routes/(console)/project-[project]/auth/(providers)/gitlabOAuth.svelte';
import Google from '$routes/(console)/project-[project]/auth/(providers)/googleOAuth.svelte';
import Main from '$routes/(console)/project-[project]/auth/(providers)/mainOAuth.svelte';
import Microsoft from '$routes/(console)/project-[project]/auth/(providers)/microsoftOAuth.svelte';
import Oidc from '$routes/(console)/project-[project]/auth/(providers)/oidcOAuth.svelte';
import Okta from '$routes/(console)/project-[project]/auth/(providers)/oktaOAuth.svelte';
import Apple from '$routes/(console)/project-[region]-[project]/auth/(providers)/appleOAuth.svelte';
import Auth0 from '$routes/(console)/project-[region]-[project]/auth/(providers)/auth0OAuth.svelte';
import Authentik from '$routes/(console)/project-[region]-[project]/auth/(providers)/authentikOAuth.svelte';
import GitLab from '$routes/(console)/project-[region]-[project]/auth/(providers)/gitlabOAuth.svelte';
import Google from '$routes/(console)/project-[region]-[project]/auth/(providers)/googleOAuth.svelte';
import Main from '$routes/(console)/project-[region]-[project]/auth/(providers)/mainOAuth.svelte';
import Microsoft from '$routes/(console)/project-[region]-[project]/auth/(providers)/microsoftOAuth.svelte';
import Oidc from '$routes/(console)/project-[region]-[project]/auth/(providers)/oidcOAuth.svelte';
import Okta from '$routes/(console)/project-[region]-[project]/auth/(providers)/oktaOAuth.svelte';
export type Provider = {
name: string;
+3 -1
View File
@@ -2,7 +2,7 @@ import { page } from '$app/stores';
import { derived, writable } from 'svelte/store';
import type { Models } from '@appwrite.io/console';
import type { Tier } from './billing';
import type { Plan } from '$lib/sdk/billing';
import type { Plan, RegionList } from '$lib/sdk/billing';
export type OrganizationError = {
status: number;
@@ -60,3 +60,5 @@ export const organizationList = derived(
export const organization = derived(page, ($page) => $page.data?.organization as Organization);
export const currentPlan = derived(page, ($page) => $page.data?.currentPlan as Plan);
export const members = derived(page, ($page) => $page.data.members as Models.MembershipList);
export const regions = writable<RegionList | undefined>(undefined);
+13 -9
View File
@@ -1,11 +1,11 @@
import { browser } from '$app/environment';
import { page } from '$app/stores';
import type { View } from '$lib/helpers/load';
import type { Page } from '@sveltejs/kit';
import { get, writable } from 'svelte/store';
import { sdk } from './sdk';
import type { Models } from '@appwrite.io/console';
import { organization } from './organization';
import { page } from '$app/state';
type Preferences = {
limit?: number;
@@ -47,7 +47,7 @@ function createPreferences() {
set,
update,
get: (route?: Page['route']): Preferences => {
const parsedRoute = route ?? get(page).route;
const parsedRoute = route ?? page.route;
return (
preferences?.[parsedRoute.id] ?? {
limit: null,
@@ -62,7 +62,7 @@ function createPreferences() {
},
setLimit: (limit: Preferences['limit']) =>
update((n) => {
const path = get(page).route.id;
const path = page.route.id;
if (!n?.[path]) {
n ??= {};
@@ -73,9 +73,9 @@ function createPreferences() {
return n;
}),
setView: (view: Preferences['view']) =>
setView: (projectId: string, route: Page['route'], view: Preferences['view']) =>
update((n) => {
const path = get(page).route.id;
const path = page.route.id;
if (!n?.[path]) {
n ??= {};
@@ -86,9 +86,9 @@ function createPreferences() {
return n;
}),
setColumns: (columns: Preferences['columns']) =>
setColumns: (projectId: string, route: Page['route'], columns: Preferences['columns']) =>
update((n) => {
const path = get(page).route.id;
const path = page.route.id;
if (!n?.[path]) {
n ??= {};
@@ -99,9 +99,13 @@ function createPreferences() {
return n;
}),
setCustomCollectionColumns: (columns: Preferences['columns']) =>
setCustomCollectionColumns: (
projectId: string,
route: Page['route'],
columns: Preferences['columns']
) =>
update((n) => {
const current = get(page);
const current = page;
const collection = current.params.collection;
if (!n?.collections?.[collection]) {
+59 -12
View File
@@ -1,4 +1,3 @@
import { getProjectId } from '$lib/helpers/project';
import { VARS } from '$lib/system';
import {
Account,
@@ -26,21 +25,52 @@ import { Billing } from '../sdk/billing';
import { Backups } from '../sdk/backups';
import { Domains } from '$lib/sdk/domains';
import { Sources } from '$lib/sdk/sources';
import {
REGION_FRA,
REGION_NYC,
REGION_SYD,
SUBDOMAIN_FRA,
SUBDOMAIN_NYC,
SUBDOMAIN_SYD
} from '$lib/constants';
import { building } from '$app/environment';
import { getProjectId } from '$lib/helpers/project';
export function getApiEndpoint(): string {
if (VARS.APPWRITE_ENDPOINT) return VARS.APPWRITE_ENDPOINT;
return globalThis?.location?.origin + '/v1';
export function getApiEndpoint(region?: string): string {
if (building) return '';
const url = new URL(
VARS.APPWRITE_ENDPOINT ? VARS.APPWRITE_ENDPOINT : globalThis?.location?.toString()
);
const protocol = url.protocol;
const hostname = url.hostname;
const subdomain = getSubdomain(region);
return `${protocol}//${subdomain}${hostname}/v1`;
}
const getSubdomain = (region?: string) => {
switch (region) {
case REGION_FRA:
return SUBDOMAIN_FRA;
case REGION_SYD:
return SUBDOMAIN_SYD;
case REGION_NYC:
return SUBDOMAIN_NYC;
default:
return '';
}
};
const endpoint = getApiEndpoint();
const clientConsole = new Client();
const clientProject = new Client();
const clientRealtime = new Client();
if (!building) {
clientConsole.setEndpoint(endpoint).setProject('console');
clientProject.setEndpoint(endpoint).setMode('admin');
clientRealtime.setEndpoint(endpoint).setProject('console');
}
const sdkForProject = {
@@ -64,12 +94,22 @@ const sdkForProject = {
sites: new Sites(clientProject)
};
export const getSdkForProject = (projectId: string) => {
if (projectId && projectId !== clientProject.config.project) {
clientProject.setProject(projectId);
}
export const realtime = {
forProject(region: string, _projectId: string) {
const endpoint = getApiEndpoint(region);
if (endpoint !== clientRealtime.config.endpoint) {
clientRealtime.setEndpoint(endpoint);
return sdkForProject;
/**
* Workaround: the SDK doesn't update the realtime in `setEndpoint`.
* Until that's fixed, we manually set the realtime endpoint like this:
*/
clientRealtime.setEndpointRealtime(
endpoint.replace('https://', 'wss://').replace('http://', 'ws://')
);
}
return clientRealtime;
}
};
export const sdk = {
@@ -91,9 +131,16 @@ export const sdk = {
sites: new Sites(clientConsole),
domains: new Domains(clientConsole)
},
get forProject() {
const projectId = getProjectId();
return getSdkForProject(projectId);
forProject(region: string, projectId: string) {
const endpoint = getApiEndpoint(region);
if (endpoint !== clientProject.config.endpoint) {
clientProject.setEndpoint(endpoint);
}
if (projectId !== clientProject.config.project) {
clientProject.setProject(projectId);
}
return sdkForProject;
}
};
+31 -21
View File
@@ -1,6 +1,8 @@
import { type Models, Sites, Storage } from '@appwrite.io/console';
import { Client, type Models, Sites, Storage } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import { createAdminClient } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import { page } from '$app/state';
type UploaderFile = {
$id: string;
@@ -17,12 +19,18 @@ export type Uploader = {
files: UploaderFile[];
};
const temporaryStorage = () => {
return new Storage(createAdminClient());
const temporaryStorage = (region: string, projectId: string) => {
const clientProject = new Client().setMode('admin');
const endpoint = getApiEndpoint(region);
clientProject.setEndpoint(endpoint).setProject(projectId);
return new Storage(clientProject);
};
const temporarySites = () => {
return new Sites(createAdminClient());
const temporarySites = (region: string, projectId: string) => {
const clientProject = new Client().setMode('admin');
const endpoint = getApiEndpoint(region);
clientProject.setEndpoint(endpoint).setProject(projectId);
return new Sites(clientProject);
};
const createUploader = () => {
@@ -64,7 +72,14 @@ const createUploader = () => {
isCollapsed: false,
files: []
}),
uploadFile: async (bucketId: string, id: string, file: File, permissions: string[]) => {
uploadFile: async (
region: string,
projectId: string,
bucketId: string,
id: string,
file: File,
permissions: string[]
) => {
const newFile: UploaderFile = {
$id: id,
resourceId: bucketId,
@@ -79,7 +94,7 @@ const createUploader = () => {
n.files.unshift(newFile);
return n;
});
const uploadedFile = await temporaryStorage().createFile(
const uploadedFile = await temporaryStorage(region, projectId).createFile(
bucketId,
id ?? 'unique()',
file,
@@ -111,20 +126,15 @@ const createUploader = () => {
n.files.unshift(newDeployment);
return n;
});
const uploadedFile = await temporarySites().createDeployment(
siteId,
code,
true,
undefined,
undefined,
undefined,
(p) => {
newDeployment.$id = p.$id;
newDeployment.progress = p.progress;
newDeployment.status = p.progress === 100 ? 'success' : 'pending';
updateFile(p.$id, newDeployment);
}
);
const uploadedFile = await temporarySites(
page.params.region,
page.params.project
).createDeployment(siteId, code, true, undefined, undefined, undefined, (p) => {
newDeployment.$id = p.$id;
newDeployment.progress = p.progress;
newDeployment.status = p.progress === 100 ? 'success' : 'pending';
updateFile(p.$id, newDeployment);
});
newDeployment.$id = uploadedFile.$id;
newDeployment.progress = 100;
newDeployment.status = 'success';
@@ -17,6 +17,6 @@ export function openMigrationWizard() {
});
}
export const selectedProject = writable(null);
export const selectedProject = writable<string>(null);
export const provider = createMigrationProviderStore();
@@ -2,11 +2,7 @@
import { Alert, Box, EyebrowHeading } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { deepMap } from '$lib/helpers/object';
import type { WritableValue } from '$lib/helpers/types';
import { type getSdkForProject } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import {
createMigrationFormStore,
createMigrationProviderStore,
@@ -16,10 +12,12 @@
} from '$lib/stores/migration';
import { wizard } from '$lib/stores/wizard';
import type { Models } from '@appwrite.io/console';
import type { sdk } from '$lib/stores/sdk';
import type { WritableValue } from '$lib/helpers/types';
export let formData: ReturnType<typeof createMigrationFormStore>;
export let provider: ReturnType<typeof createMigrationProviderStore>;
export let projectSdk: ReturnType<typeof getSdkForProject>;
export let projectSdk: ReturnType<typeof sdk.forProject>;
type ValueOf<T> = T[keyof T];
type FormData = WritableValue<typeof formData>;
@@ -1,7 +1,6 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { getSdkForProject } from '$lib/stores/sdk';
import { sdk } from '$lib/stores/sdk';
import { formData, provider, selectedProject } from '.';
import ResourceForm from './resource-form.svelte';
</script>
@@ -18,5 +17,5 @@
Learn about which resources are supported</a
>.
</svelte:fragment>
<ResourceForm {provider} {formData} projectSdk={getSdkForProject($selectedProject)} />
<ResourceForm {provider} {formData} projectSdk={sdk.forProject('fra1', $selectedProject)} />
</WizardStep>
@@ -3,7 +3,6 @@
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
import Wizard from '$lib/layout/wizardWithSteps.svelte';
import { migrationFormToResources } from '$lib/stores/migration';
import { getSdkForProject } from '$lib/stores/sdk';
import { wizard } from '$lib/stores/wizard';
import { onDestroy } from 'svelte';
import { formData, provider, selectedProject } from '.';
@@ -11,6 +10,7 @@
import Step2 from './step2.svelte';
import { requestedMigration } from '$routes/store';
import { base } from '$app/paths';
import { sdk } from '$lib/stores/sdk';
const onExit = () => {
formData.reset();
@@ -21,12 +21,14 @@
if ($provider.provider !== 'appwrite') return;
const resources = migrationFormToResources($formData, $provider.provider);
await getSdkForProject($selectedProject).migrations.createAppwriteMigration(
resources,
$provider.endpoint,
$provider.projectID,
$provider.apiKey
);
await sdk
.forProject('fra1', $selectedProject)
.migrations.createAppwriteMigration(
resources,
$provider.endpoint,
$provider.projectID,
$provider.apiKey
);
await invalidateAll();
wizard.hide();
+11 -9
View File
@@ -30,7 +30,7 @@
import { addSubPanel } from '$lib/commandCenter/subPanels';
import { addNotification } from '$lib/stores/notifications';
import { openMigrationWizard } from './(migration-wizard)';
import { project } from './project-[project]/store';
import { project } from './project-[region]-[project]/store';
import { feedback } from '$lib/stores/feedback';
import { hasStripePublicKey, isCloud, VARS } from '$lib/system';
import { stripe } from '$lib/stores/stripe';
@@ -171,7 +171,9 @@
({
label: kebabToSentenceCase(heading),
async callback() {
await goto(`${base}/project-${$project.$id}/auth/security#${heading}`);
await goto(
`${base}/project-${$project.region}-${$project.$id}/auth/security#${heading}`
);
scrollBy({ top: -100 });
},
disabled: !$project?.$id,
@@ -185,7 +187,7 @@
keys: isOnSettingsLayout ? ['g', 'o'] : undefined,
callback: () => {
goto(`${base}/project-${$project.$id}/settings`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings`);
},
disabled:
!$project?.$id || (isOnSettingsLayout && page.url.pathname.endsWith('settings')),
@@ -197,7 +199,7 @@
keys: isOnSettingsLayout ? ['g', 'd'] : undefined,
callback: () => {
goto(`${base}/project-${$project.$id}/settings/domains`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings/domains`);
},
disabled:
!$project?.$id || (isOnSettingsLayout && page.url.pathname.includes('domains')),
@@ -208,7 +210,7 @@
label: 'Go to webhooks',
keys: isOnSettingsLayout ? ['g', 'w'] : undefined,
callback: () => {
goto(`${base}/project-${$project.$id}/settings/webhooks`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings/webhooks`);
},
disabled:
!$project?.$id || (isOnSettingsLayout && page.url.pathname.includes('webhooks')),
@@ -220,7 +222,7 @@
label: 'Go to migrations',
keys: isOnSettingsLayout ? ['g', 'm'] : undefined,
callback: () => {
goto(`${base}/project-${$project.$id}/settings/migrations`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings/migrations`);
},
disabled:
!$project?.$id || (isOnSettingsLayout && page.url.pathname.includes('migrations')),
@@ -232,7 +234,7 @@
label: 'Go to SMTP settings',
keys: isOnSettingsLayout ? ['g', 's'] : undefined,
callback: () => {
goto(`${base}/project-${$project.$id}/settings/smtp`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings/smtp`);
},
disabled: !$project?.$id || (isOnSettingsLayout && page.url.pathname.includes('smtp')),
group: isOnSettingsLayout ? 'navigation' : 'settings',
@@ -287,9 +289,9 @@
}
database.subscribe(async (database) => {
if (!database) return;
if (!database || !$page.params.region || !$page.params.project) return;
// the component checks `isCloud` internally.
await checkForDatabaseBackupPolicies(database);
await checkForDatabaseBackupPolicies($page.params.region, $page.params.project, database);
});
let currentOrganizationId = null;
@@ -6,7 +6,7 @@ import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url, route }) => {
const page = getPage(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit('console', url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
return {
@@ -7,7 +7,7 @@ import { isCloud } from '$lib/system';
export const load: PageLoad = async ({ url, route }) => {
const page = getPage(url);
const limit = getLimit(url, route, CARD_LIMIT);
const limit = getLimit('console', url, route, CARD_LIMIT);
const offset = pageToOffset(page, limit);
const queries = [Query.offset(offset), Query.limit(limit), Query.orderDesc('')];
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { page } from '$app/stores';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
@@ -27,8 +28,10 @@
let error: string = null;
onMount(async () => {
const countryList = await sdk.forProject.locale.listCountries();
const locale = await sdk.forProject.locale.get();
const countryList = await sdk
.forProject($page.params.region, $page.params.project)
.locale.listCountries();
const locale = await sdk.forProject($page.params.region, $page.params.project).locale.get();
if (locale.countryCode) {
country = locale.countryCode;
}
@@ -28,6 +28,7 @@
Tag,
Typography
} from '@appwrite.io/pink-svelte';
import { page } from '$app/state';
let show = false;
let showEdit = false;
@@ -37,7 +38,9 @@
let countryList: Models.CountryList;
onMount(async () => {
countryList = await sdk.forProject.locale.listCountries();
countryList = await sdk
.forProject(page.params.region, page.params.project)
.locale.listCountries();
});
$: orgList = $organizationList.teams as unknown as Organization[];
@@ -1,5 +1,6 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { page } from '$app/state';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Dependencies } from '$lib/constants';
@@ -21,7 +22,13 @@
];
onMount(async () => {
const countryList = await sdk.forProject.locale.listCountries();
const countryList = await sdk
.forProject(page.params.region, page.params.project)
.locale.listCountries();
const locale = await sdk.forProject(page.params.region, page.params.project).locale.get();
if (locale.countryCode) {
selectedAddress.country = locale.countryCode;
}
options = countryList.countries.map((country) => {
return {
value: country.code,
@@ -62,11 +69,13 @@
* which really shouldn't happen, but here we are, playing it safe!
*/
$: if (show && !selectedAddress.country) {
sdk.forProject.locale.get().then((locale) => {
if (locale.countryCode) {
selectedAddress.country = locale.countryCode;
}
});
sdk.forProject(page.params.region, page.params.project)
.locale.get()
.then((locale) => {
if (locale.countryCode) {
selectedAddress.country = locale.countryCode;
}
});
}
</script>
@@ -20,7 +20,8 @@
function getBrowser(clientCode: string) {
const code = clientCode.toLowerCase();
if (!isValueOfStringEnum(Browser, code)) return '';
return sdk.forProject.avatars.getBrowser(code, 40, 40);
return sdk.forConsole.avatars.getBrowser(code, 40, 40);
}
function logoutSessionId(sessionId: string) {
+1 -1
View File
@@ -21,7 +21,7 @@ if (isCloud) {
importance: 8,
cta: {
text: 'Try now',
link: ({ project }) => `${base}/project-${project.$id}/databases`
link: ({ project }) => `${base}/project-${project.region}-${project.$id}/databases`
},
learnMore: {
text: 'Learn more',
@@ -13,6 +13,7 @@ import { preferences } from '$lib/stores/preferences';
import type { Organization } from '$lib/stores/organization';
import { defaultRoles, defaultScopes } from '$lib/constants';
import type { Plan } from '$lib/sdk/billing';
import { loadAvailableRegions } from '$routes/(console)/regions';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.ORGANIZATION);
@@ -52,6 +53,8 @@ export const load: LayoutLoad = async ({ params, depends }) => {
preferences.loadTeamPrefs(params.organization)
]);
await loadAvailableRegions(params.organization);
return {
header: Header,
breadcrumbs: Breadcrumbs,
@@ -22,7 +22,7 @@
import { loading } from '$routes/store';
import type { Models } from '@appwrite.io/console';
import { ID, Region } from '@appwrite.io/console';
import { openImportWizard } from '../project-[project]/settings/migrations/(import)';
import { openImportWizard } from '../project-[region]-[project]/settings/migrations/(import)';
import { readOnly } from '$lib/stores/billing';
import { onMount, type ComponentType } from 'svelte';
import { canWriteProjects } from '$lib/stores/roles';
@@ -122,17 +122,16 @@
trackEvent(Submit.ProjectCreate, {
teamId: page.params.organization
});
await goto(`${base}/project-${project.$id}/settings/migrations`);
await goto(`${base}/project-${project.region}-${project.$id}/settings/migrations`);
openImportWizard();
loading.set(false);
} catch (e) {
trackError(e, Submit.ProjectCreate);
}
};
onMount(async () => {
if (isCloud) {
const regions = await sdk.forConsole.billing.listRegions();
if (isCloud && $organization.$id) {
const regions = await sdk.forConsole.billing.listRegions($organization.$id);
regionsStore.set(regions);
checkPricingRefAndRedirect(page.url.searchParams);
}
@@ -9,7 +9,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const { scopes } = await parent();
depends(Dependencies.ORGANIZATION);
const page = getPage(url);
const limit = getLimit(url, route, CARD_LIMIT);
const limit = getLimit('console', url, route, CARD_LIMIT);
const offset = pageToOffset(page, limit);
if (!scopes.includes('projects.read') && scopes.includes('billing.read')) {
return redirect(301, `/console/organization-${params.organization}/billing`);
@@ -11,6 +11,7 @@
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { base } from '$app/paths';
import { Alert, Badge, Card, Layout, Skeleton } from '@appwrite.io/pink-svelte';
import { page } from '$app/state';
export let show = false;
let loading = true;
@@ -43,11 +44,13 @@
: null
: null;
const locale = await sdk.forProject.locale.get();
const locale = await sdk.forProject(page.params.region, page.params.project).locale.get();
if (locale?.countryCode) {
country = locale.countryCode;
}
const countryList = await sdk.forProject.locale.listCountries();
const countryList = await sdk
.forProject(page.params.region, page.params.project)
.locale.listCountries();
options = countryList.countries.map((country) => {
return {
value: country.code,
@@ -41,7 +41,7 @@
type: 'success',
message: `${name} has been created`
});
await goto(`${base}/project-${project.$id}`);
await goto(`${base}/project-${project.region}-${project.$id}`);
} catch (e) {
error = e.message;
trackError(e, Submit.ProjectCreate);
@@ -41,7 +41,7 @@
message: `${$createProject.name} has been created`
});
await onFinish();
await goto(`${base}/project-${project.$id}`);
await goto(`${base}/project-${project.region}-${project.$id}`);
} catch (e) {
addNotification({
type: 'error',
@@ -9,7 +9,7 @@ export const load: PageLoad = async ({ url, params, route, depends }) => {
depends(Dependencies.MEMBERS);
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit('console', url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
return {
@@ -39,8 +39,10 @@
let error: string;
onMount(async () => {
const countryList = await sdk.forProject.locale.listCountries();
const locale = await sdk.forProject.locale.get();
/* use console sdk as project is not always available here. */
const locale = await sdk.forConsole.locale.get();
const countryList = await sdk.forConsole.locale.listCountries();
if (locale.countryCode) {
country = locale.countryCode;
}
@@ -40,8 +40,10 @@
let error: string;
onMount(async () => {
const countryList = await sdk.forProject.locale.listCountries();
const locale = await sdk.forProject.locale.get();
/* use console sdk as project is not always available here. */
const locale = await sdk.forConsole.locale.get();
const countryList = await sdk.forConsole.locale.listCountries();
if (locale.countryCode) {
country = locale.countryCode;
}
@@ -56,7 +56,7 @@ export const load: PageLoad = async ({ params, parent }) => {
sdk.forConsole.billing.getOrganizationPlan(org.$id)
]);
const projectNames: { [key: string]: Models.Project } = {};
const projects: { [key: string]: Models.Project } = {};
if (usage?.projects?.length > 0) {
// in batches of 100 (the max number of values in a query)
const requests = [];
@@ -75,17 +75,17 @@ export const load: PageLoad = async ({ params, parent }) => {
const responses = await Promise.all(requests);
for (const response of responses) {
for (const project of response.projects) {
projectNames[project.$id] = project;
projects[project.$id] = project;
}
}
}
return {
organizationUsage: usage,
projectNames,
plan,
invoices,
projects,
currentInvoice,
organizationMembers,
plan
organizationUsage: usage
};
};
@@ -37,7 +37,12 @@
}
function getProjectUsageLink(projectId: string): string {
return `${base}/project-${projectId}/settings/usage`;
const region = data.projects[projectId]?.region ?? 'fra';
return `${base}/project-${region}-${projectId}/settings/usage`;
}
function getProjectName(projectId: string): string {
return data.projects[projectId]?.name ?? 'Unknown';
}
function groupByProject(
@@ -53,12 +58,14 @@
}> {
const data = [];
for (const project of projects) {
const projectId = project.projectId;
if (metric) {
const usage = project[metric];
if (!usage) continue;
data.push({
projectId: project.projectId,
projectId,
usage: usage ?? 0,
estimate: estimate ? project[estimate] : undefined
});
@@ -68,7 +75,7 @@
if (reads || writes) {
data.push({
projectId: project.projectId,
projectId,
databasesReads: reads,
databasesWrites: writes
});
@@ -1,6 +1,6 @@
<script lang="ts">
import { BackupRestoreBox, MigrationBox, UploadBox } from '$lib/components';
import { sdk } from '$lib/stores/sdk';
import { realtime } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { project, stats } from './store';
@@ -26,20 +26,22 @@
} from '$lib/stores/roles';
onMount(() => {
return sdk.forConsole.client.subscribe(['project', 'console'], (response) => {
if (response.events.includes('stats.connections')) {
for (const [projectId, value] of Object.entries(response.payload)) {
stats.add(projectId, [new Date(response.timestamp).toISOString(), value]);
return realtime
.forProject($page.params.region, $page.params.project)
.subscribe(['project', 'console'], (response) => {
if (response.events.includes('stats.connections')) {
for (const [projectId, value] of Object.entries(response.payload)) {
stats.add(projectId, [new Date(response.timestamp).toISOString(), value]);
}
}
}
});
});
});
$: $registerCommands([
{
label: 'Go to Auth',
callback: () => {
goto(`${base}/project-${$project.$id}/auth`);
goto(`${base}/project-${$project.region}-${$project.$id}/auth`);
},
keys: ['g', 'a'],
group: 'navigation'
@@ -47,7 +49,7 @@
{
label: 'Go to Databases',
callback: () => {
goto(`${base}/project-${$project.$id}/databases`);
goto(`${base}/project-${$project.region}-${$project.$id}/databases`);
},
keys: ['g', 'd'],
group: 'navigation',
@@ -56,7 +58,7 @@
{
label: 'Go to Functions',
callback: () => {
goto(`${base}/project-${$project.$id}/functions`);
goto(`${base}/project-${$project.region}-${$project.$id}/functions`);
},
keys: ['g', 'f'],
group: 'navigation',
@@ -65,7 +67,7 @@
{
label: 'Go to Messaging',
callback: () => {
goto(`${base}/project-${$project.$id}/messaging`);
goto(`${base}/project-${$project.region}-${$project.$id}/messaging`);
},
keys: ['g', 'm'],
disabled: page.url.pathname.endsWith('messaging') || !$canSeeMessages,
@@ -74,7 +76,7 @@
{
label: 'Go to Storage',
callback: () => {
goto(`${base}/project-${$project.$id}/storage`);
goto(`${base}/project-${$project.region}-${$project.$id}/storage`);
},
keys: ['g', 's'],
group: 'navigation',
@@ -83,7 +85,7 @@
{
label: 'Go to Settings',
callback: () => {
goto(`${base}/project-${$project.$id}/settings`);
goto(`${base}/project-${$project.region}-${$project.$id}/settings`);
},
keys: ['g', 'e'],
group: 'navigation',
@@ -101,7 +103,7 @@
{
label: 'Go to Overview',
callback: () => {
goto(`${base}/project-${$project.$id}`);
goto(`${base}/project-${$project.region}-${$project.$id}`);
},
keys: ['g', 'o'],
group: 'navigation'
@@ -11,6 +11,7 @@ import type { Plan } from '$lib/sdk/billing';
import { get } from 'svelte/store';
import { headerAlert } from '$lib/stores/headerAlert';
import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte';
import { loadAvailableRegions } from '$routes/(console)/regions';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.PROJECT);
@@ -18,9 +19,10 @@ export const load: LayoutLoad = async ({ params, depends }) => {
try {
const project = await sdk.forConsole.projects.get(params.project);
const [organization, prefs] = await Promise.all([
const [organization, prefs, _] = await Promise.all([
sdk.forConsole.teams.get(project.teamId) as Promise<Organization>,
sdk.forConsole.account.getPrefs()
sdk.forConsole.account.getPrefs(),
loadAvailableRegions(project.teamId)
]);
if (prefs?.organization !== project.teamId) {
sdk.forConsole.account.updatePrefs({
@@ -5,7 +5,7 @@ import { hasOnboardingDismissed } from '$lib/helpers/onboarding';
export const load: PageLoad = async ({ params }) => {
if (hasOnboardingDismissed(params.project)) {
redirect(302, `${base}/project-${params.project}/overview`);
redirect(302, `${base}/project-${params.region}-${params.project}/overview`);
}
redirect(302, `${base}/project-${params.project}/get-started`);
redirect(302, `${base}/project-${params.region}-${params.project}/get-started`);
};
@@ -10,12 +10,7 @@
import { Link, Alert } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
export let provider: Models.AuthProvider;
export let show = false;
let appId: string = null;
let enabled: boolean = null;
let clientSecret: string = null;
let auth0Domain: string = null;
let error: string;
@@ -17,7 +17,7 @@
label: 'Create user',
callback: async () => {
if (!page.url.pathname.endsWith('auth')) {
await goto(`${base}/project-${$project.$id}/auth`);
await goto(`${base}/project-${$project.region}-${$project.$id}/auth`);
}
showCreateUser.set(true);
},
@@ -31,7 +31,7 @@
label: 'Create team',
callback: async () => {
if (!page.url.pathname.endsWith('teams')) {
await goto(`${base}/project-${$project.$id}/auth/teams`);
await goto(`${base}/project-${$project.region}-${$project.$id}/auth/teams`);
}
showCreateTeam.set(true);
},
@@ -46,7 +46,7 @@
label: 'Go to teams',
keys: ['g', 't'],
callback() {
goto(`${base}/project-${$project.$id}/auth/teams`);
goto(`${base}/project-${$project.region}-${$project.$id}/auth/teams`);
},
group: 'navigation',
rank: 1,
@@ -56,7 +56,7 @@
label: 'Go to usage',
keys: ['g', 'u'],
callback() {
goto(`${base}/project-${$project.$id}/auth/usage`);
goto(`${base}/project-${$project.region}-${$project.$id}/auth/usage`);
},
group: 'navigation',
rank: 1,
@@ -66,7 +66,7 @@
label: 'Go to security',
keys: ['g', 'e'],
callback() {
goto(`${base}/project-${$project.$id}/auth/security`);
goto(`${base}/project-${$project.region}-${$project.$id}/auth/security`);
},
group: 'navigation',
rank: 1,
@@ -76,7 +76,7 @@
label: 'Go to settings',
keys: ['g', 's'],
callback() {
goto(`${base}/project-${$project.$id}/auth/settings`);
goto(`${base}/project-${$project.region}-${$project.$id}/auth/settings`);
},
group: 'navigation',
rank: 1,
@@ -19,7 +19,6 @@
import { Container } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import type { PageData } from './$types';
import Create from './createUser.svelte';
import { Badge, Icon, Table, Layout, Typography } from '@appwrite.io/pink-svelte';
import { Tag } from '@appwrite.io/pink-svelte';
@@ -29,9 +28,8 @@
import ViewSelector from '$lib/components/viewSelector.svelte';
import { View } from '$lib/helpers/load';
export let data: PageData;
export let data;
const projectId = page.params.project;
const columns = writable<Column[]>([
{ id: '$id', title: 'User ID', type: 'string', width: 200 },
{ id: 'name', title: 'Name', type: 'string', width: { min: 260 } },
@@ -49,7 +47,9 @@
]);
async function userCreated(event: CustomEvent<Models.User<Record<string, unknown>>>) {
await goto(`${base}/project-${projectId}/auth/user-${event.detail.$id}`);
await goto(
`${base}/project-${page.params.region}-${page.params.project}/auth/user-${event.detail.$id}`
);
}
</script>
@@ -75,7 +75,9 @@
{/each}
</svelte:fragment>
{#each data.users.users as user}
<Table.Row.Link href={`${base}/project-${projectId}/auth/user-${user.$id}`} {root}>
<Table.Row.Link
href={`${base}/project-${page.params.region}-${page.params.project}/auth/user-${user.$id}`}
{root}>
{#each $columns as { id } (id)}
<Table.Cell column={id} {root}>
{#if id === '$id'}
@@ -160,8 +162,10 @@
total={data.users.total} />
{:else if data.search}
<EmptySearch target="users" hidePagination>
<Button href={`${base}/project-${projectId}/auth`} size="s" secondary
>Clear Search</Button>
<Button
href={`${base}/project-${page.params.region}-${page.params.project}/auth`}
size="s"
secondary>Clear Search</Button>
</EmptySearch>
{:else}
<Empty
@@ -5,10 +5,10 @@ import { PAGE_LIMIT } from '$lib/constants';
import type { PageLoad } from './$types';
import { showCreateUser } from './+page.svelte';
export const load: PageLoad = async ({ url, route }) => {
export const load: PageLoad = async ({ url, route, params }) => {
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit(params.project, url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
if (typeof url.searchParams.get('create') === 'string') {
@@ -20,9 +20,8 @@ export const load: PageLoad = async ({ url, route }) => {
limit,
search,
page,
users: await sdk.forProject.users.list(
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
)
users: await sdk
.forProject(params.region, params.project)
.users.list([Query.limit(limit), Query.offset(offset), Query.orderDesc('')], search)
};
};
@@ -10,11 +10,11 @@
title: $organization?.name
},
{
href: `${base}/project-${$project?.$id}`,
href: `${base}/project-${$project.region}-${$project?.$id}`,
title: $project?.name
},
{
href: `${base}/project-${$project?.$id}/auth`,
href: `${base}/project-${$project.region}-${$project?.$id}/auth`,
title: 'Auth'
}
];
@@ -1,4 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal, CustomId } from '$lib/components';
import { InputText, Button } from '$lib/elements/forms';
@@ -18,7 +19,9 @@
const create = async () => {
try {
const team = await sdk.forProject.teams.create(id ?? ID.unique(), name);
const team = await sdk
.forProject($page.params.region, $page.params.project)
.teams.create(id ?? ID.unique(), name);
name = '';
showCreate = false;
showCustomId = false;
@@ -1,4 +1,5 @@
<script lang="ts">
import { page } from '$app/stores';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Modal, CustomId } from '$lib/components';
import { Button, InputPassword, InputEmail, InputText, InputPhone } from '$lib/elements/forms';
@@ -19,13 +20,15 @@
const create = async () => {
try {
const user = await sdk.forProject.users.create(
id ?? ID.unique(),
mail || undefined,
phone || undefined,
pass || undefined,
name || undefined
);
const user = await sdk
.forProject($page.params.region, $page.params.project)
.users.create(
id ?? ID.unique(),
mail || undefined,
phone || undefined,
pass || undefined,
name || undefined
);
mail = pass = name = '';
showCreate = false;
showDropdown = false;
@@ -7,8 +7,7 @@
import { canWriteProjects } from '$lib/stores/roles';
import { Typography } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
const path = `${base}/project-${projectId}/auth`;
const path = `${base}/project-${page.params.region}-${page.params.project}/auth`;
const tabs = [
{
href: path,
@@ -17,14 +17,13 @@
import { Container } from '$lib/layout';
import { base } from '$app/paths';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import { writable } from 'svelte/store';
import { canWriteTeams } from '$lib/stores/roles';
import { Icon, Layout, Table } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import DualTimeView from '$lib/components/dualTimeView.svelte';
export let data: PageData;
export let data;
const project = page.params.project;
const teamCreated = async (event: CustomEvent<Models.Team<Record<string, unknown>>>) => {
@@ -79,7 +78,10 @@
total={data.teams.total} />
{:else if data.search}
<EmptySearch target="teams" search={data.search} hidePagination={data.teams.total === 0}>
<Button secondary size="s" href={`${base}/project-${page.params.project}/auth/teams`}>
<Button
secondary
size="s"
href={`${base}/project-${page.params.region}-${page.params.project}/auth/teams`}>
Clear Search
</Button>
</EmptySearch>
@@ -4,10 +4,10 @@ import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
import { PAGE_LIMIT } from '$lib/constants';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ url, route }) => {
export const load: PageLoad = async ({ url, route, params }) => {
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit(params.project, url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
return {
@@ -15,9 +15,8 @@ export const load: PageLoad = async ({ url, route }) => {
limit,
search,
page,
teams: await sdk.forProject.teams.list(
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
)
teams: await sdk
.forProject(params.region, params.project)
.teams.list([Query.limit(limit), Query.offset(offset), Query.orderDesc('')], search)
};
};
@@ -11,7 +11,7 @@ export const load: LayoutLoad = async ({ params, depends }) => {
return {
header: Header,
breadcrumbs: Breadcrumbs,
team: await sdk.forProject.teams.get(params.team)
team: await sdk.forProject(params.region, params.project).teams.get(params.team)
};
} catch (e) {
error(e.code, e.message);
@@ -6,15 +6,17 @@ import type { PageLoad } from './$types';
export const load: PageLoad = async ({ params, url, route }) => {
const page = getPage(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit(params.project, url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
return {
offset,
limit,
logs: await sdk.forProject.teams.listLogs(params.team, [
Query.limit(limit),
Query.offset(offset)
])
logs: await sdk
.forProject(params.region, params.project)
.databases.listDocumentLogs(params.database, params.collection, params.document, [
Query.limit(limit),
Query.offset(offset)
])
};
};
@@ -1,5 +1,6 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Breadcrumbs } from '$lib/layout';
import { organization } from '$lib/stores/organization';
import { project } from '../../../store';
@@ -11,15 +12,15 @@
title: $organization?.name
},
{
href: `${base}/project-${$project.$id}`,
href: `${base}/project-${$page.params.region}-${$page.params.project}`,
title: $project?.name
},
{
href: `${base}/project-${$project?.$id}/auth`,
href: `${base}/project-${$page.params.region}-${$page.params.project}/auth`,
title: 'Auth'
},
{
href: `${base}/project-${$project?.$id}/auth/teams/team-${$team?.$id}`,
href: `${base}/project-${$page.params.region}-${$page.params.project}/auth/teams/team-${$team?.$id}`,
title: $team?.name
}
];
@@ -19,15 +19,17 @@
async function create() {
try {
const user = await sdk.forProject.teams.createMembership(
teamId,
roles,
email || undefined,
undefined,
undefined,
`${page.url.origin}${base}/invite`,
name || undefined
);
const user = await sdk
.forProject(page.params.region, page.params.project)
.teams.createMembership(
teamId,
roles,
email || undefined,
undefined,
undefined,
`${page.url.origin}${base}/invite`,
name || undefined
);
addNotification({
type: 'success',
message: `${name ? name : email} created successfully`
@@ -18,15 +18,14 @@
async function deleteMembership() {
try {
await sdk.forProject.teams.deleteMembership(
selectedMembership.teamId,
selectedMembership.$id
);
await sdk
.forProject(page.params.region, page.params.project)
.teams.deleteMembership(selectedMembership.teamId, selectedMembership.$id);
showDelete = false;
dispatch('deleted');
trackEvent(Submit.MemberDelete);
await goto(
`${base}/project-${page.params.project}/auth/teams/team-${selectedMembership.teamId}/members`
`${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${selectedMembership.teamId}/members`
);
} catch (e) {
error = e.message;
@@ -15,10 +15,10 @@
async function deleteTeam() {
try {
await sdk.forProject.teams.delete(team.$id);
await sdk.forProject(page.params.region, page.params.project).teams.delete(team.$id);
showDelete = false;
trackEvent(Submit.TeamDelete);
await goto(`${base}/project-${page.params.project}/auth/teams`);
await goto(`${base}/project-${page.params.region}-${page.params.project}/auth/teams`);
} catch (e) {
error = e.message;
trackError(e, Submit.TeamDelete);
@@ -6,9 +6,7 @@
import { Cover, CoverTitle } from '$lib/layout';
import { team } from './store';
const projectId = page.params.project;
const teamId = page.params.team;
const path = `${base}/project-${projectId}/auth/teams/team-${teamId}`;
const path = `${base}/project-${page.params.region}-${page.params.project}/auth/teams/team-${page.params.team}`;
const tabs = [
{
href: path,
@@ -32,7 +30,8 @@
<Cover>
<svelte:fragment slot="header">
<CoverTitle href={`${base}/project-${projectId}/auth/teams`}>
<CoverTitle
href={`${base}/project-${page.params.region}-${page.params.project}/auth/teams`}>
{$team?.name}
</CoverTitle>
<Id value={$team?.$id} event="team">{$team?.$id}</Id>
@@ -10,17 +10,19 @@ export const load: PageLoad = async ({ params, depends, url, route }) => {
const teamId = params.team;
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const limit = getLimit(params.project, url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
return {
offset,
search,
limit,
memberships: await sdk.forProject.teams.listMemberships(
teamId,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
)
memberships: await sdk
.forProject(params.region, params.project)
.teams.listMemberships(
teamId,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
)
};
};
@@ -18,7 +18,9 @@
async function updateName() {
try {
await sdk.forProject.teams.updateName(page.params.team, teamName);
await sdk
.forProject(page.params.region, page.params.project)
.teams.updateName(page.params.team, teamName);
await invalidate(Dependencies.TEAM);
addNotification({
message: 'Name has been updated',
@@ -10,6 +10,7 @@
import { team } from './store';
import { Icon, Layout } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { page } from '$app/state';
$: if (prefs) {
if (JSON.stringify(prefs) !== JSON.stringify(Object.entries($team.prefs))) {
@@ -37,7 +38,9 @@
try {
let updatedPrefs = Object.fromEntries(prefs);
await sdk.forProject.teams.updatePrefs($team.$id, updatedPrefs);
await sdk
.forProject(page.params.region, page.params.project)
.teams.updatePrefs($team.$id, updatedPrefs);
await invalidate(Dependencies.TEAM);
arePrefsDisabled = true;
@@ -58,11 +58,11 @@
EmailTemplateType,
EmailTemplateLocale
} from '@appwrite.io/console';
import { Accordion, Layout, Alert, Badge, Link, Typography } from '@appwrite.io/pink-svelte';
import { Accordion, Alert, Layout, Link, Typography } from '@appwrite.io/pink-svelte';
export let data;
let emailOpen = null;
let emailOpen = 'verification';
$: emailVerificationOpen = emailOpen === 'verification';
$: emailMagicSessionOpen = emailOpen === 'magicSession';
$: emailOtpSessionOpen = emailOpen === 'otpSession';
@@ -1,7 +1,7 @@
import { sdk } from '$lib/stores/sdk';
export const load = async () => {
const codes = await sdk.forProject.locale.listCodes();
export const load = async ({ params }) => {
const codes = await sdk.forProject(params.region, params.project).locale.listCodes();
return {
localeCodes: codes.localeCodes
};
@@ -2,16 +2,15 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import { Container, Usage } from '$lib/layout';
import type { PageData } from './$types';
export let data: PageData;
export let data;
$: total = data.usersTotal;
$: count = data.users;
</script>
<Container>
<Usage
path={`${base}/project-${page.params.project}/auth/usage`}
path={`${base}/project-${page.params.region}-${page.params.project}/auth/usage`}
countMetadata={{
legend: 'Users',
title: 'Registered users'

Some files were not shown because too many files have changed in this diff Show More