mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' of github.com:appwrite/console into feat-v2-regions
This commit is contained in:
+3
-1
@@ -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=
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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'
|
||||
},
|
||||
|
||||
Generated
+5
-5
@@ -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:
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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})`;
|
||||
|
||||
@@ -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
@@ -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,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);
|
||||
|
||||
@@ -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
@@ -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>
|
||||
|
||||
+2
-2
@@ -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
-2
@@ -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>
|
||||
|
||||
+2
-2
@@ -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" />
|
||||
|
||||
+1
-1
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user