Merge branch 'main' of github.com:appwrite/console into feat-v2-regions

This commit is contained in:
Arman
2025-05-02 14:43:37 +02:00
39 changed files with 145 additions and 194 deletions
+3 -1
View File
@@ -1,4 +1,6 @@
PUBLIC_APPWRITE_ENDPOINT=https://localhost/v1
PUBLIC_CONSOLE_MODE=self-hosted
PUBLIC_APPWRITE_MULTI_REGION=false
PUBLIC_APPWRITE_ENDPOINT=http://localhost/v1
PUBLIC_STRIPE_KEY=
PUBLIC_GROWTH_ENDPOINT=
+3
View File
@@ -39,6 +39,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"PUBLIC_CONSOLE_MODE=cloud"
"PUBLIC_APPWRITE_MULTI_REGION=true"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY }}"
"SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}"
@@ -77,6 +78,7 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"PUBLIC_CONSOLE_MODE=cloud"
"PUBLIC_APPWRITE_MULTI_REGION=true"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
"PUBLIC_STRIPE_KEY=${{ secrets.PUBLIC_STRIPE_KEY_STAGE }}"
publish-self-hosted:
@@ -113,4 +115,5 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
build-args: |
"PUBLIC_CONSOLE_MODE=self-hosted"
"PUBLIC_APPWRITE_MULTI_REGION=false"
"PUBLIC_GROWTH_ENDPOINT=${{ secrets.PUBLIC_GROWTH_ENDPOINT }}"
+3 -1
View File
@@ -21,6 +21,7 @@ ADD ./src /app/src
ADD ./static /app/static
ARG PUBLIC_CONSOLE_MODE
ARG PUBLIC_APPWRITE_MULTI_REGION
ARG PUBLIC_APPWRITE_ENDPOINT
ARG PUBLIC_GROWTH_ENDPOINT
ARG PUBLIC_STRIPE_KEY
@@ -30,6 +31,7 @@ ARG SENTRY_RELEASE
ENV PUBLIC_APPWRITE_ENDPOINT=$PUBLIC_APPWRITE_ENDPOINT
ENV PUBLIC_GROWTH_ENDPOINT=$PUBLIC_GROWTH_ENDPOINT
ENV PUBLIC_CONSOLE_MODE=$PUBLIC_CONSOLE_MODE
ENV PUBLIC_APPWRITE_MULTI_REGION=$PUBLIC_APPWRITE_MULTI_REGION
ENV PUBLIC_STRIPE_KEY=$PUBLIC_STRIPE_KEY
ENV SENTRY_AUTH_TOKEN=$SENTRY_AUTH_TOKEN
ENV SENTRY_RELEASE=$SENTRY_RELEASE
@@ -37,7 +39,7 @@ ENV NODE_OPTIONS=--max_old_space_size=8192
RUN pnpm run build
FROM nginx:1.25-alpine
FROM nginx:1.26.3-alpine
EXPOSE 80
+1
View File
@@ -24,6 +24,7 @@ async function main() {
log(bold().magenta('APPWRITE CONSOLE'));
log();
logEnv('CONSOLE MODE', env?.PUBLIC_CONSOLE_MODE);
logEnv('MULTI REGION', env?.PUBLIC_APPWRITE_MULTI_REGION);
logEnv('APPWRITE ENDPOINT', env?.PUBLIC_APPWRITE_ENDPOINT, 'relative');
logEnv('GROWTH ENDPOINT', env?.PUBLIC_GROWTH_ENDPOINT);
log();
+2
View File
@@ -5,6 +5,7 @@ services:
context: .
args:
PUBLIC_CONSOLE_MODE: ${PUBLIC_CONSOLE_MODE}
PUBLIC_APPWRITE_MULTI_REGION: ${PUBLIC_APPWRITE_MULTI_REGION}
PUBLIC_APPWRITE_ENDPOINT: ${PUBLIC_APPWRITE_ENDPOINT}
PUBLIC_GROWTH_ENDPOINT: ${PUBLIC_GROWTH_ENDPOINT}
PUBLIC_STRIPE_KEY: ${PUBLIC_STRIPE_KEY}
@@ -20,6 +21,7 @@ services:
- build/
environment:
- PUBLIC_CONSOLE_MODE
- PUBLIC_APPWRITE_MULTI_REGION
- PUBLIC_APPWRITE_ENDPOINT
- PUBLIC_GROWTH_ENDPOINT
- PUBLIC_STRIPE_KEY
+9 -9
View File
@@ -1,8 +1,6 @@
map $sent_http_content_type $expires {
# cache everything for 1 year
default 1y;
# html files shouldn't be cached for single-page applications
text/html off;
map $sent_http_content_type $cc_header {
default "public, max-age=31536000";
text/html "no-cache";
}
server {
@@ -12,15 +10,17 @@ server {
# serve compressed file if filename.gz exists
gzip_static on;
location = /console/service-worker.js {
root /usr/share/nginx/html;
add_header Cache-Control "public, max-age=30" always;
}
location /console {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri /console/index.html;
# Add cache headers
expires $expires;
add_header Pragma public;
add_header Cache-Control "public";
add_header Cache-Control $cc_header always;
# Deny IE browsers from going into quirks mode
add_header X-UA-Compatible "IE=Edge";
+2 -2
View File
@@ -21,7 +21,8 @@
"e2e:ui": "playwright test --ui"
},
"dependencies": {
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e",
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f",
@@ -32,7 +33,6 @@
"@stripe/stripe-js": "^3.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.16",
"@ai-sdk/svelte": "^1.1.24",
"cron-parser": "^4.9.0",
"dayjs": "^1.11.13",
"deep-equal": "^2.2.3",
+1
View File
@@ -15,6 +15,7 @@ const config: PlaywrightTestConfig = {
env: {
PUBLIC_APPWRITE_ENDPOINT: 'https://stage.cloud.appwrite.io/v1',
PUBLIC_CONSOLE_MODE: 'cloud',
PUBLIC_APPWRITE_MULTI_REGION: 'true',
PUBLIC_STRIPE_KEY:
'pk_test_51LT5nsGYD1ySxNCyd7b304wPD8Y1XKKWR6hqo6cu3GIRwgvcVNzoZv4vKt5DfYXL1gRGw4JOqE19afwkJYJq1g3K004eVfpdWn'
},
+5 -5
View File
@@ -12,8 +12,8 @@ importers:
specifier: ^1.1.24
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
'@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
specifier: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1
version: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1
'@appwrite.io/pink':
specifier: 0.25.0
version: 0.25.0
@@ -254,8 +254,8 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1':
resolution: {tarball: https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1}
version: 1.2.1
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f':
@@ -3625,7 +3625,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@e2f082e': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite/appwrite/@appwrite.io/console@f76aff1': {}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@bd21ff7f(svelte@5.25.3)':
dependencies:
+17 -5
View File
@@ -5,18 +5,30 @@
import { isSupportOnline, showSupportModal } from '$routes/(console)/wizard/support/store';
import { Click, trackEvent } from '$lib/actions/analytics';
import { localeShortTimezoneName, utcHourToLocaleHour } from '$lib/helpers/date';
import { upgradeURL } from '$lib/stores/billing';
import { plansInfo } from '$lib/stores/billing';
import { Card } from '$lib/components/index';
import { app } from '$lib/stores/app';
import { currentPlan } from '$lib/stores/organization';
import { currentPlan, type Organization, organizationList } from '$lib/stores/organization';
import { isCloud } from '$lib/system';
import { Typography } from '@appwrite.io/pink-svelte';
import { base } from '$app/paths';
export let show = false;
export let showHeader = true;
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? false;
$: hasPremiumSupport = $currentPlan?.premiumSupport ?? allOrgsHavePremiumSupport ?? false;
$: allOrgsHavePremiumSupport = $organizationList.teams.every(
(team) => $plansInfo.get((team as Organization).billingPlan)?.premiumSupport
);
// there can only be one free organization
$: freeOrganization = $organizationList.teams.find(
(team) => !$plansInfo.get((team as Organization).billingPlan)?.premiumSupport
);
$: upgradeURL = `${base}/organization-${freeOrganization.$id}/change-plan`;
$: supportTimings = `${utcHourToLocaleHour('16:00')} - ${utcHourToLocaleHour('00:00')} ${localeShortTimezoneName()}`;
@@ -55,7 +67,7 @@
}
];
const showCloudSupport = (index) => {
const showCloudSupport = (index: number) => {
return (index === 0 && isCloud) || index > 0;
};
</script>
@@ -79,7 +91,7 @@
<div class="u-flex u-gap-12 u-cross-center">
{#if !hasPremiumSupport}
<Button
href={$upgradeURL}
href={upgradeURL}
on:click={() => {
trackEvent(Click.OrganizationClickUpgrade, {
from: 'button',
+7
View File
@@ -53,3 +53,10 @@ export function formatNum(number: number): string {
* Returns a regex to check hostname validity. Supports wildcards too!
*/
export const hostnameRegex = String.raw`(\*)|(\*\.)?(?!-)[A-Za-z0-9\-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}|localhost`;
/**
* Returns a regex to check hostname validity.
*
* Supports domains, localhost, wildcards, ip-addresses and Chrome extension IDs!
*/
export const extendedHostnameRegex = String.raw`(\*)|(\*\.)?((?!-)[A-Za-z0-9\-]+([\-\.]{1}[a-z0-9]+)*\.[A-Za-z]{2,18}|localhost|(\d{1,3}\.){3}\d{1,3}|[a-z0-9]{32})`;
+9 -2
View File
@@ -15,8 +15,15 @@
domain.set({ $id: '', domain: '' });
return realtime
.forProject(page.params.region, page.params.region)
.subscribe<Models.ProxyRule>('console', (data) => domain.set(data.payload));
.forProject(page.params.region, page.params.project)
.subscribe<Models.ProxyRule>('console', (message) => {
if (
message.channels.includes(`projects.${page.params.project}`) &&
message.events.includes('rules.*')
) {
domain.set(message.payload);
}
});
});
async function onFinish() {
+1 -14
View File
@@ -261,19 +261,6 @@ export type AllowedRegions =
| 'syd'
| 'default'; //TODO: remove after migration
export type Region = {
$id: AllowedRegions;
name: string;
disabled: boolean;
default: boolean;
flag: string;
};
export type RegionList = {
regions: Region[];
total: number;
};
export type Address = {
$id: string;
streetAddress: string;
@@ -1277,7 +1264,7 @@ export class Billing {
);
}
async listRegions(teamId: string): Promise<RegionList> {
async listRegions(teamId: string): Promise<Models.ConsoleRegionList> {
const path = `/console/regions`;
const params = {
teamId
+2 -2
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, RegionList } from '$lib/sdk/billing';
import type { Plan } from '$lib/sdk/billing';
export type OrganizationError = {
status: number;
@@ -61,4 +61,4 @@ export const organization = derived(page, ($page) => $page.data?.organization as
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);
export const regions = writable<Models.ConsoleRegionList | undefined>(undefined);
+4 -9
View File
@@ -43,7 +43,9 @@ export function getApiEndpoint(region?: string): string {
);
const protocol = url.protocol;
const hostname = url.hostname;
const subdomain = getSubdomain(region);
// If instance supports multi-region, add the region subdomain.
const subdomain = VARS.APPWRITE_MULTI_REGION ? getSubdomain(region) : '';
return `${protocol}//${subdomain}${hostname}/v1`;
}
@@ -69,6 +71,7 @@ const clientRealtime = new Client();
if (!building) {
clientConsole.setEndpoint(endpoint).setProject('console');
clientRealtime.setEndpoint(endpoint).setProject('console');
clientProject.setEndpoint(endpoint).setMode('admin');
clientRealtime.setEndpoint(endpoint).setProject('console');
}
@@ -99,14 +102,6 @@ export const realtime = {
const endpoint = getApiEndpoint(region);
if (endpoint !== clientRealtime.config.endpoint) {
clientRealtime.setEndpoint(endpoint);
/**
* 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;
}
+2 -1
View File
@@ -10,7 +10,8 @@ export const VARS = {
CONSOLE_MODE: (env.PUBLIC_CONSOLE_MODE as Mode) ?? undefined,
APPWRITE_ENDPOINT: env.PUBLIC_APPWRITE_ENDPOINT ?? undefined,
GROWTH_ENDPOINT: env.PUBLIC_GROWTH_ENDPOINT ?? undefined,
PUBLIC_STRIPE_KEY: env.PUBLIC_STRIPE_KEY ?? undefined
PUBLIC_STRIPE_KEY: env.PUBLIC_STRIPE_KEY ?? undefined,
APPWRITE_MULTI_REGION: env.PUBLIC_APPWRITE_MULTI_REGION ?? false
};
export const ENV = {
@@ -25,10 +25,6 @@
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,
@@ -39,7 +39,7 @@
} from '@appwrite.io/pink-icons-svelte';
import { getPlatformInfo } from '$lib/helpers/platform';
import CreateProjectCloud from './createProjectCloud.svelte';
import { regions as regionsStore } from '$routes/(console)/organization-[organization]/store';
import { organization, regions as regionsStore } from '$lib/stores/organization';
export let data;
@@ -117,7 +117,7 @@
ID.unique(),
`Imported project ${new Date().toISOString()}`,
page.params.organization,
Region.Default
Region.Fra // default
);
trackEvent(Submit.ProjectCreate, {
teamId: page.params.organization
@@ -11,7 +11,7 @@
import { formatCurrency } from '$lib/helpers/numbers.js';
import { Wizard } from '$lib/layout';
import { type Coupon, type PaymentList } from '$lib/sdk/billing';
import { isOrganization, plansInfo, tierToPlan, type Tier } from '$lib/stores/billing';
import { isOrganization, plansInfo, type Tier, tierToPlan } from '$lib/stores/billing';
import { addNotification } from '$lib/stores/notifications';
import { currentPlan, organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
@@ -63,8 +63,7 @@
if (page.url.searchParams.has('code')) {
const coupon = page.url.searchParams.get('code');
try {
const response = await sdk.forConsole.billing.getCouponAccount(coupon);
couponData = response;
couponData = await sdk.forConsole.billing.getCouponAccount(coupon);
} catch (e) {
couponData = {
code: null,
@@ -179,7 +178,7 @@
async function upgrade() {
try {
//Add collaborators
// Add collaborators
let newCollaborators = [];
if (collaborators?.length) {
newCollaborators = collaborators.filter(
@@ -6,7 +6,7 @@
import { InputText, Button } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { ID, Region } from '@appwrite.io/console';
import { ID } from '@appwrite.io/console';
import { IconPencil } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Tag } from '@appwrite.io/pink-svelte';
import { createEventDispatcher } from 'svelte';
@@ -25,12 +25,7 @@
async function create() {
try {
disabled = true;
const project = await sdk.forConsole.projects.create(
id ?? ID.unique(),
name,
teamId,
Region.Default
);
const project = await sdk.forConsole.projects.create(id ?? ID.unique(), name, teamId);
show = false;
dispatch('created', project);
trackEvent(Submit.ProjectCreate, {
@@ -1,10 +1,9 @@
import { page } from '$app/stores';
import type { Models } from '@appwrite.io/console';
import { derived, writable } from 'svelte/store';
import type { RegionList } from '$lib/sdk/billing';
import { derived } from 'svelte/store';
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { regions } from '$lib/stores/organization';
export const regions = writable<RegionList | undefined>(undefined);
export const regionFlagUrls = derived(regions, ($regions) => {
if (!$regions?.regions?.length) return [];
@@ -5,13 +5,13 @@ import type { LayoutLoad } from './$types';
import { preferences } from '$lib/stores/preferences';
import { failedInvoice } from '$lib/stores/billing';
import { isCloud } from '$lib/system';
import type { Organization } from '$lib/stores/organization';
import { defaultRoles, defaultScopes } from '$lib/constants';
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';
import type { Organization } from '$lib/stores/organization';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.PROJECT);
@@ -30,7 +30,7 @@ export const load: LayoutLoad = async ({ params, depends }) => {
organization: project.teamId
});
}
preferences.loadTeamPrefs(project.teamId);
await preferences.loadTeamPrefs(project.teamId);
let roles = isCloud ? [] : defaultRoles;
let scopes = isCloud ? [] : defaultScopes;
if (isCloud) {
@@ -3,10 +3,10 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputSwitch, InputText, InputTextarea } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
import { Link, Alert } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
@@ -70,7 +70,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,7 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -11,6 +11,8 @@
const projectId = page.params.project;
export let provider: Models.AuthProvider;
export let show = false;
let clientSecret: string = null;
let auth0Domain: string = null;
let error: string;
@@ -70,7 +72,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,7 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -78,7 +78,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,11 +3,11 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
import { Link, Alert } from '@appwrite.io/pink-svelte';
import { getApiEndpoint } from '$lib/stores/sdk';
const projectId = page.params.project;
@@ -73,7 +73,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,8 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -68,9 +69,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${
sdk.forConsole.client.config.endpoint
}/account/sessions/oauth2/callback/${provider.name.toLocaleLowerCase()}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
@@ -2,12 +2,12 @@
import { page } from '$app/state';
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { Link, Alert } from '@appwrite.io/pink-svelte';
import { getApiEndpoint } from '$lib/stores/sdk';
const projectId = page.params.project;
@@ -68,7 +68,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,8 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -75,7 +76,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,8 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -113,7 +114,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -3,7 +3,8 @@
import { CopyInput, Modal } from '$lib/components';
import { Button, InputPassword, InputSwitch, InputText } from '$lib/elements/forms';
import { oAuthProviders, type Provider } from '$lib/stores/oauth-providers';
import { sdk } from '$lib/stores/sdk';
import { getApiEndpoint } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { onMount } from 'svelte';
import { updateOAuth } from '../updateOAuth';
import type { Models } from '@appwrite.io/console';
@@ -85,7 +86,7 @@
<div>
<p>URI</p>
<CopyInput
value={`${sdk.forConsole.client.config.endpoint}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
value={`${getApiEndpoint(page.params.region)}/account/sessions/oauth2/callback/${provider.key}/${projectId}`} />
</div>
<svelte:fragment slot="footer">
<Button secondary on:click={() => (provider = null)}>Cancel</Button>
@@ -91,7 +91,7 @@
<Button
compact
slot="actions"
href={`${base}/project-${data.project.$id}/settings/smtp`}>
href={`${base}/project-${data.project.region}-${data.project.$id}/settings/smtp`}>
SMTP settings
</Button>
</Alert.Inline>
@@ -182,7 +182,9 @@
</Layout.Stack>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button href={`${base}/project-${data.project.$id}/settings/smtp`} secondary>
<Button
href={`${base}/project-${data.project.region}-${data.project.$id}/settings/smtp`}
secondary>
SMTP settings
</Button>
</svelte:fragment>
@@ -8,7 +8,7 @@
import { onMount } from 'svelte';
import { project } from '../../../store';
import { platform } from './store';
import { hostnameRegex } from '$lib/helpers/string';
import { extendedHostnameRegex } from '$lib/helpers/string';
let hostname: string = null;
@@ -49,7 +49,7 @@
id="hostname"
label="Hostname"
bind:value={hostname}
pattern={hostnameRegex}
pattern={extendedHostnameRegex}
patternError="Please enter a valid hostname"
required
placeholder="myapp.com" />
@@ -17,7 +17,7 @@
export let data;
$: baseRoute = `${base}/project-${data.project.$id}`;
$: baseRoute = `${base}/project-${data.project.region}-${data.project.$id}`;
$: network = data.usage.network;
$: users = data.usage.users;
$: usersTotal = data.usage.usersTotal;
@@ -3,31 +3,26 @@ import { sdk } from '$lib/stores/sdk';
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';
export const load: PageLoad = async ({ params, depends, url, route, parent }) => {
const { organization } = await parent();
export const load: PageLoad = async ({ params, depends, url, route }) => {
depends(Dependencies.FILES);
const page = getPage(url);
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
const [files, organizationUsage] = await Promise.all([
sdk
.forProject(params.region, params.project)
.storage.listFiles(
params.bucket,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
),
isCloud && organization?.$id ? sdk.forConsole.billing.listUsage(organization.$id) : null
]);
const files = await sdk
.forProject(params.region, params.project)
.storage.listFiles(
params.bucket,
[Query.limit(limit), Query.offset(offset), Query.orderDesc('')],
search
);
return {
offset,
limit,
search,
files,
organizationUsage
files
};
};
+2 -2
View File
@@ -293,14 +293,14 @@
input[type='radio'],
input[type='checkbox']:not([class='switch']),
input[type='switchbox'] {
.theme-dark &:not(:checked) {
.theme-dark &:not(:checked):not(:disabled) {
background-color: transparent; /* take whatever color is behind */
border: 1px solid var(--color-mid-neutral-70, #56565c);
}
}
input[type='checkbox'][class='switch'] {
.theme-dark &:not(:checked) {
.theme-dark &:not(:checked):not(:disabled) {
background-color: var(--color-mid-neutral-70, #56565c);
}
}
+7 -72
View File
@@ -3,88 +3,23 @@
/// <reference lib="esnext" />
/// <reference lib="webworker" />
import { build, files, version, base } from '$service-worker';
import { build, files } from '$service-worker';
const sw = self as unknown as ServiceWorkerGlobalScope;
// Create a unique cache name for this deployment
const CACHE = `cache-${version}`;
const ASSETS = [
...build, // the app itself
...files // everything in `static`
];
// Preload all assets
sw.addEventListener('install', (event) => {
// Create a new cache and add all files to it
async function addFilesToCache() {
const cache = await caches.open(CACHE);
await cache.addAll(ASSETS);
}
// Check if the page URL does not match the exclusion path
const shouldCache = !location.pathname.startsWith(`${base}/auth`);
if (shouldCache) {
event.waitUntil(addFilesToCache());
}
event.waitUntil(Promise.allSettled(ASSETS.map((asset) => fetch(asset))));
});
// Clean up all old caches left by previous service workers
sw.addEventListener('activate', (event) => {
// Remove previous cached data from disk
async function deleteOldCaches() {
for (const key of await caches.keys()) {
if (key !== CACHE) await caches.delete(key);
}
}
event.waitUntil(deleteOldCaches());
});
sw.addEventListener('fetch', (event) => {
// ignore POST requests etc
if (event.request.method !== 'GET') return;
async function respond() {
const url = new URL(event.request.url);
const cache = await caches.open(CACHE);
// `build`/`files` can always be served from the cache
if (ASSETS.includes(url.pathname)) {
const response = await cache.match(url.pathname);
if (response) {
return response;
}
}
// for everything else, try the network first, but
// fall back to the cache if we're offline
try {
const response = await fetch(event.request);
// if we're offline, fetch can return a value that is not a Response
// instead of throwing - and we can't pass this non-Response to respondWith
if (!(response instanceof Response)) {
throw new Error('invalid response from fetch');
}
if (response.status === 200) {
cache.put(event.request, response.clone());
}
return response;
} catch (err) {
const response = await cache.match(event.request);
if (response) {
return response;
}
// if there's no cache, then just error out
// as there is nothing we can do to respond to this request
throw err;
}
}
event.respondWith(respond());
event.waitUntil(
caches.keys().then((keys) => Promise.all(keys.map((key) => caches.delete(key))))
);
});
Binary file not shown.
+11 -5
View File
@@ -16,15 +16,21 @@ Object.defineProperty(window, 'matchMedia', {
});
beforeAll(() => {
vi.mock('$app/environment', () => ({
dev: true,
building: true,
browser: true,
page: {
params: {
project: 'tests',
region: 'tests'
}
}
}));
vi.mock('$app/navigation', () => ({
goto: vi.fn(),
beforeNavigate: vi.fn()
}));
vi.mock('$app/environment', () => ({
dev: true,
browser: true,
building: true
}));
vi.mock('$env/static/public', () => import.meta.env);
vi.mock('$env/dynamic/public', () => ({
env: import.meta.env