diff --git a/package.json b/package.json
index b804dae27..37a8b8b4b 100644
--- a/package.json
+++ b/package.json
@@ -22,12 +22,13 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
- "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@d7e354b",
+ "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@865e2fc",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@865e2fc",
"@faker-js/faker": "^9.9.0",
+ "@plausible-analytics/tracker": "^0.4.4",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
@@ -42,7 +43,6 @@
"ignore": "^6.0.2",
"nanoid": "^5.1.5",
"nanotar": "^0.1.1",
- "plausible-tracker": "^0.3.9",
"pretty-bytes": "^6.1.1",
"prismjs": "^1.30.0",
"remarkable": "^2.0.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8aee84871..c112c59e6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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.vc/-/@appwrite/@appwrite.io/console@d7e354b
- version: https://pkg.vc/-/@appwrite/@appwrite.io/console@d7e354b
+ specifier: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
+ version: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -29,6 +29,9 @@ importers:
'@faker-js/faker':
specifier: ^9.9.0
version: 9.9.0
+ '@plausible-analytics/tracker':
+ specifier: ^0.4.4
+ version: 0.4.4
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -71,9 +74,6 @@ importers:
nanotar:
specifier: ^0.1.1
version: 0.1.1
- plausible-tracker:
- specifier: ^0.3.9
- version: 0.3.9
pretty-bytes:
specifier: ^6.1.1
version: 6.1.1
@@ -272,9 +272,9 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
- '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@d7e354b':
- resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@d7e354b}
- version: 1.10.0
+ '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755':
+ resolution: {tarball: https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755}
+ version: 2.1.0
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
@@ -1008,6 +1008,9 @@ packages:
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
+ '@plausible-analytics/tracker@0.4.4':
+ resolution: {integrity: sha512-fz0NOYUEYXtg1TBaPEEvtcBq3FfmLFuTe1VZw4M8sTWX129br5dguu3M15+plOQnc181ShYe67RfwhKgK89VnA==}
+
'@playwright/test@1.56.1':
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
engines: {node: '>=18'}
@@ -2900,10 +2903,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
- plausible-tracker@0.3.9:
- resolution: {integrity: sha512-hMhneYm3GCPyQon88SZrVJx+LlqhM1kZFQbuAgXPoh/Az2YvO1B6bitT9qlhpiTdJlsT5lsr3gPmzoVjb5CDXA==}
- engines: {node: '>=10'}
-
playwright-core@1.56.1:
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
engines: {node: '>=18'}
@@ -3823,7 +3822,7 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
- '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@d7e354b': {}
+ '@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@9c55755': {}
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
dependencies:
@@ -4575,6 +4574,8 @@ snapshots:
'@parcel/watcher-win32-x64': 2.5.1
optional: true
+ '@plausible-analytics/tracker@0.4.4': {}
+
'@playwright/test@1.56.1':
dependencies:
playwright: 1.56.1
@@ -6675,8 +6676,6 @@ snapshots:
picomatch@4.0.3: {}
- plausible-tracker@0.3.9: {}
-
playwright-core@1.56.1: {}
playwright@1.56.1:
diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts
index 6880f1c2f..cfd42ae8f 100644
--- a/src/lib/actions/analytics.ts
+++ b/src/lib/actions/analytics.ts
@@ -1,5 +1,5 @@
import Analytics, { type AnalyticsPlugin } from 'analytics';
-import Plausible from 'plausible-tracker';
+import { init, track } from '@plausible-analytics/tracker';
import { get } from 'svelte/store';
import { page } from '$app/state';
import { user } from '$lib/stores/user';
@@ -11,30 +11,30 @@ import { getReferrerAndUtmSource, getTrackedQueryParams } from '$lib/helpers/utm
function plausible(domain: string): AnalyticsPlugin {
if (!browser) return { name: 'analytics-plugin-plausible' };
- const instance = Plausible({
- domain
+ init({
+ domain,
+ autoCapturePageviews: false
});
return {
name: 'analytics-plugin-plausible',
page: ({ payload }) => {
- instance.trackPageview({
+ track('pageview', {
url: payload.properties.path,
- referrer: payload.properties.referrer,
- deviceWidth: payload.properties.width
+ props: {
+ referrer: payload.properties.referrer,
+ deviceWidth: String(payload.properties.width)
+ }
});
},
track: ({ payload }) => {
- instance.trackEvent(
- payload.event,
- {
- props: payload.properties
- },
- {
- url: payload.properties.path,
- deviceWidth: payload.properties.width
+ track(payload.event, {
+ url: payload.properties.path,
+ props: {
+ ...payload.properties,
+ deviceWidth: String(payload.properties.width)
}
- );
+ });
},
loaded: () => true
};
@@ -247,6 +247,7 @@ export enum Submit {
ProjectDelete = 'submit_project_delete',
ProjectUpdateName = 'submit_project_update_name',
ProjectUpdateTeam = 'submit_project_update_team',
+ ProjectUpdateLabels = 'submit_project_update_labels',
ProjectService = 'submit_project_service',
ProjectUpdateSMTP = 'submit_project_update_smtp',
MemberCreate = 'submit_member_create',
@@ -268,7 +269,7 @@ export enum Submit {
AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update',
AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update',
AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update',
- AuthInvalidateSesssion = 'submit_auth_invalidate_session',
+ AuthInvalidateSession = 'submit_auth_invalidate_session',
SessionsLengthUpdate = 'submit_sessions_length_update',
SessionsLimitUpdate = 'submit_sessions_limit_update',
SessionDelete = 'submit_session_delete',
@@ -277,6 +278,7 @@ export enum Submit {
DatabaseDelete = 'submit_database_delete',
DatabaseUpdateName = 'submit_database_update_name',
DatabaseImportCsv = 'submit_database_import_csv',
+ DatabaseBackupDelete = 'submit_database_backup_delete',
ColumnCreate = 'submit_column_create',
ColumnUpdate = 'submit_column_update',
diff --git a/src/lib/components/archiveProject.svelte b/src/lib/components/archiveProject.svelte
index a8b736284..8a324b395 100644
--- a/src/lib/components/archiveProject.svelte
+++ b/src/lib/components/archiveProject.svelte
@@ -1,4 +1,5 @@
@@ -194,7 +205,7 @@
+ badge={`${archivedTotalOverall}`}>
{#if isPlanBelowPro}
These projects are archived and require a plan upgrade to restore access.
@@ -204,7 +215,7 @@
-
+
{#each projectsToArchive as project}
{@const platforms = filterPlatforms(
project.platforms.map((platform) => getPlatformInfo(platform.type))
@@ -264,7 +275,7 @@
{/each}
- {#if platforms.length > 3}
+ {#if platforms.length > 2}
{/each}
+
+
@@ -353,4 +373,7 @@
align-items: center;
gap: 8px;
}
+ :global(.pagination-container) {
+ margin-top: 16px;
+ }
diff --git a/src/lib/components/backupRestoreBox.svelte b/src/lib/components/backupRestoreBox.svelte
index f1de9d3ba..05cb96e8f 100644
--- a/src/lib/components/backupRestoreBox.svelte
+++ b/src/lib/components/backupRestoreBox.svelte
@@ -5,7 +5,6 @@
import { isCloud, isSelfHosted } from '$lib/system';
import { organization } from '$lib/stores/organization';
import { BillingPlan, Dependencies } from '$lib/constants';
- import type { BackupArchive, BackupRestoration } from '$lib/sdk/backups';
import { goto, invalidate } from '$app/navigation';
import { page } from '$app/state';
import { addNotification } from '$lib/stores/notifications';
@@ -13,10 +12,11 @@
import { getProjectId } from '$lib/helpers/project';
import { toLocaleDate } from '$lib/helpers/date';
import { Typography } from '@appwrite.io/pink-svelte';
+ import { type Models } from '@appwrite.io/console';
const backupRestoreItems: {
- archives: Map;
- restorations: Map;
+ archives: Map;
+ restorations: Map;
} = {
archives: new Map(),
restorations: new Map()
@@ -117,7 +117,7 @@
if (which === 'restorations') lastDatabaseRestorationId = null;
}
- function backupName(item: BackupArchive | BackupRestoration, key: string) {
+ function backupName(item: Models.BackupArchive | Models.BackupRestoration, key: string) {
const column = key === 'archives' ? '$createdAt' : 'startedAt';
return toLocaleDate(item[column]);
diff --git a/src/lib/components/billing/alerts/paymentMandate.svelte b/src/lib/components/billing/alerts/paymentMandate.svelte
index 0126f2f9a..e69de29bb 100644
--- a/src/lib/components/billing/alerts/paymentMandate.svelte
+++ b/src/lib/components/billing/alerts/paymentMandate.svelte
@@ -1,25 +0,0 @@
-
-
-{#if $paymentMissingMandate && $paymentMissingMandate?.country?.toLowerCase() === 'in' && $paymentMissingMandate.mandateId === null && !hideBillingHeaderRoutes.includes(page.url.pathname)}
-
- The payment method for {$organization.name} needs to be verified.
-
- Verify payment method
-
-
-{/if}
diff --git a/src/lib/components/domains/cnameTable.svelte b/src/lib/components/domains/cnameTable.svelte
index b198524da..9569ce89f 100644
--- a/src/lib/components/domains/cnameTable.svelte
+++ b/src/lib/components/domains/cnameTable.svelte
@@ -35,16 +35,22 @@
-
+
- Type
- Name
- Value
+ Type
+ Name
+ Value
- CNAME
- {subdomain}
-
+ CNAME
+ {subdomain}
+
-
+
- Type
- Name
- Value
+ Type
+ Name
+ Value
- {variant.toUpperCase()}
- {subdomain || '@'}
-
+ {variant.toUpperCase()}
+ {subdomain || '@'}
+
{#if $regionalConsoleVariables._APP_DOMAIN_TARGET_CAA}
-
+
CAA
- @
-
+ @
+
diff --git a/src/lib/components/limit.svelte b/src/lib/components/limit.svelte
index c8e72a401..4221f7693 100644
--- a/src/lib/components/limit.svelte
+++ b/src/lib/components/limit.svelte
@@ -8,6 +8,8 @@
export let sum: number;
export let limit: number;
export let name: string;
+ export let pageParam: string = 'page';
+ export let removeOnFirstPage: boolean = false;
const options = [
{ label: '6', value: 6 },
@@ -23,10 +25,17 @@
url.searchParams.set('limit', limit.toString());
await preferences.setLimit(limit);
- if (url.searchParams.has('page')) {
- const page = Number(url.searchParams.get('page'));
- const newPage = Math.floor(((page - 1) * previousLimit) / limit);
- url.searchParams.set('page', newPage.toString());
+ if (url.searchParams.has(pageParam)) {
+ const page = Number(url.searchParams.get(pageParam));
+ const prev =
+ Number.isFinite(previousLimit) && previousLimit > 0 ? previousLimit : limit;
+ const newPage = Math.floor(((page - 1) * prev) / limit) + 1;
+ const safePage = Math.max(1, Number.isFinite(newPage) ? newPage : 1);
+ if (removeOnFirstPage && safePage === 1) {
+ url.searchParams.delete(pageParam);
+ } else {
+ url.searchParams.set(pageParam, safePage.toString());
+ }
}
await goto(url.toString());
diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte
index cd35b38e6..ce0196e34 100644
--- a/src/lib/components/multiSelectTable.svelte
+++ b/src/lib/components/multiSelectTable.svelte
@@ -1,5 +1,13 @@
{#key computeKey}
@@ -104,13 +186,7 @@
if (confirmDeletion) {
showConfirmDeletion = true;
} else {
- const state = await onDelete?.(selectedRows);
- if (state instanceof Error) {
- // user should handle error on their own!
- } else {
- notifySuccess();
- selectedRows = [];
- }
+ await consumeDeleteOperation();
}
}}>Delete
@@ -129,16 +205,13 @@
disableModal = true;
onDeleteError = null;
- const state = await onDelete?.(selectedRows);
- if (state instanceof Error) {
- disableModal = false;
- onDeleteError = state.message || `Failed to delete ${resource}s`;
- } else {
- notifySuccess();
- selectedRows = [];
- disableModal = false;
+ const allDeleted = await consumeDeleteOperation();
+
+ if (allDeleted) {
showConfirmDeletion = false;
}
+
+ disableModal = false;
}}>
{@const selectionCount = selectedRows.length}
diff --git a/src/lib/components/pagination.svelte b/src/lib/components/pagination.svelte
index f272f7f66..975e4cf6e 100644
--- a/src/lib/components/pagination.svelte
+++ b/src/lib/components/pagination.svelte
@@ -6,15 +6,21 @@
export let limit: number;
export let offset: number;
export let useCreateLink = true;
+ export let pageParam: string = 'page';
+ export let removeOnFirstPage: boolean = false;
$: currentPage = Math.floor(offset / limit + 1);
function getLink(page: number): string {
const url = new URL(pageStore.url);
if (page === 1) {
- url.searchParams.delete('page');
+ if (removeOnFirstPage) {
+ url.searchParams.delete(pageParam);
+ } else {
+ url.searchParams.set(pageParam, '1');
+ }
} else {
- url.searchParams.set('page', page.toString());
+ url.searchParams.set(pageParam, page.toString());
}
return url.toString();
diff --git a/src/lib/components/paginationWithLimit.svelte b/src/lib/components/paginationWithLimit.svelte
index 7cf78f6b5..b48fbc0f6 100644
--- a/src/lib/components/paginationWithLimit.svelte
+++ b/src/lib/components/paginationWithLimit.svelte
@@ -8,13 +8,19 @@
offset,
total,
name,
- useCreateLink = true
+ useCreateLink = true,
+ pageParam = 'page',
+ removeOnFirstPage = false,
+ ...restProps
}: {
limit: number;
offset: number;
total: number;
name: string;
useCreateLink?: boolean;
+ pageParam?: string;
+ removeOnFirstPage?: boolean;
+ [key: string]: unknown;
} = $props();
const showLimit = $derived(!!useCreateLink);
@@ -22,10 +28,17 @@
const alignItems = $derived(showLimit ? 'center' : 'flex-end');
-
+
{#if showLimit}
-
+
{/if}
-
+
diff --git a/src/lib/components/paginator.svelte b/src/lib/components/paginator.svelte
index ae8aaf9c4..053e2ea14 100644
--- a/src/lib/components/paginator.svelte
+++ b/src/lib/components/paginator.svelte
@@ -13,7 +13,8 @@
name = 'items',
gap = 's',
offset = $bindable(0),
- children
+ children,
+ ...restProps
}: {
items: T[];
limit?: number;
@@ -26,6 +27,7 @@
| undefined;
offset?: number;
children: Snippet<[T[], number]>;
+ [key: string]: unknown;
} = $props();
let total = $derived(items.length);
@@ -33,7 +35,7 @@
let paginatedItems = $derived(items.slice(offset, offset + limit));
-
+
{@render children(paginatedItems, limit)}
{#if !hideFooter}
diff --git a/src/lib/components/searchQuery.svelte b/src/lib/components/searchQuery.svelte
index d34c9b4d6..a0b89a212 100644
--- a/src/lib/components/searchQuery.svelte
+++ b/src/lib/components/searchQuery.svelte
@@ -1,6 +1,6 @@
diff --git a/src/lib/helpers/buildTimeout.ts b/src/lib/helpers/buildTimeout.ts
index 3915811c2..df10478ee 100644
--- a/src/lib/helpers/buildTimeout.ts
+++ b/src/lib/helpers/buildTimeout.ts
@@ -22,14 +22,24 @@ function isBuildTimedOut(createdAt: string, status: string, timeoutSeconds: numb
* Gets the effective status for a build, considering timeout
*/
export function getEffectiveBuildStatus(
- originalStatus: string,
- createdAt: string,
+ deployment: Models.Deployment,
consoleVariables: Models.ConsoleVariables | undefined
): string {
+ const originalStatus = deployment.status;
+ const createdAt = deployment.$createdAt;
+
const timeoutSeconds = getBuildTimeoutSeconds(consoleVariables);
if (isBuildTimedOut(createdAt, originalStatus, timeoutSeconds)) {
return 'failed';
}
+
+ const isReady = originalStatus === 'ready';
+ const hasScreenshot = deployment.screenshotLight && deployment.screenshotDark;
+
+ if (isReady && !hasScreenshot) {
+ return 'finalizing';
+ }
+
return originalStatus;
}
diff --git a/src/lib/helpers/date.ts b/src/lib/helpers/date.ts
index 2d56c1cb6..29878256a 100644
--- a/src/lib/helpers/date.ts
+++ b/src/lib/helpers/date.ts
@@ -206,6 +206,18 @@ export function timeFromNow(datetime: string): string {
return dayjs().to(dayjs(datetime));
}
+export function timeFromNowShort(datetime: string): string {
+ if (!datetime || !isValidDate(datetime)) {
+ return 'n/a';
+ }
+
+ const timeStr = dayjs().to(dayjs(datetime));
+ return timeStr
+ .replace('second', 'sec') // seconds > secs
+ .replace('minute', 'min') // minutes > mins
+ .replace('hour', 'hr'); // hours > hrs
+}
+
export function hoursToDays(hours: number) {
if (hours > 24) {
return `${Math.floor(hours / 24)} days`;
diff --git a/src/lib/layout/activity.svelte b/src/lib/layout/activity.svelte
index dce36d66f..c77fa3488 100644
--- a/src/lib/layout/activity.svelte
+++ b/src/lib/layout/activity.svelte
@@ -1,9 +1,9 @@
@@ -29,55 +49,57 @@
expanded={databasesScreen && !insideSideSheet}
slotSpacing={databasesScreen && !insideSideSheet}>
{#if logs.total}
-
-
-
- User
- Event
- Location
- IP
- Date
-
- {#each logs.logs as log}
-
-
-
- {#if log.userEmail}
- {#if log.userName}
-
- {log.userName}
- {:else}
-
- {log.userEmail}
- {/if}
+
+
+ User
+ Event
+ Location
+ IP
+ Date
+
+ {#each logs.logs as log}
+
+
+
+ {#if log.userEmail}
+ {#if log.userName}
+
+ {log.userName}
{:else}
-
-
-
- {log.userName ?? 'Anonymous'}
+
+ {log.userEmail}
{/if}
-
-
-
- {log.event}
-
-
- {#if log.countryCode !== '--'}
- {log.countryName}
{:else}
- Unknown
+
+
+
+ {log.userName ?? 'Anonymous'}
{/if}
-
-
- {log.ip}
-
-
- {toLocaleDateTime(log.time)}
-
-
- {/each}
-
-
+
+
+
+
+ {log.event}
+
+
+
+ {#if log.countryCode !== '--'}
+ {log.countryName}
+ {:else}
+ Unknown
+ {/if}
+
+
+
+
+
+
+
+
+
+
+ {/each}
+
{
- if (typeof services === 'undefined') {
- throw new AppwriteException('Missing required parameter: "services"');
- }
- const apiPath = '/backups/archives';
- const payload: Payload = {};
- if (typeof services !== 'undefined') {
- payload['services'] = services;
- }
- if (typeof resourceId !== 'undefined') {
- payload['resourceId'] = resourceId;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('post', uri, apiHeaders, payload);
- }
-
- async deleteArchive(archiveId: string): Promise {
- if (typeof archiveId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "archiveId"');
- }
- const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
- const payload: Payload = {};
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('delete', uri, apiHeaders, payload);
- }
-
- async listArchives(queries?: string[]): Promise {
- const apiPath = '/backups/archives';
- const payload: Payload = {};
- if (typeof queries !== 'undefined') {
- payload['queries'] = queries;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-
- async getArchive(archiveId: string): Promise {
- if (typeof archiveId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "archiveId"');
- }
- const apiPath = '/backups/archives/{archiveId}'.replace('{archiveId}', archiveId);
- const payload: Payload = {};
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-
- async listPolicies(queries?: string[]): Promise {
- const apiPath = '/backups/policies';
- const payload: Payload = {};
- if (typeof queries !== 'undefined') {
- payload['queries'] = queries;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-
- async createPolicy(
- policyId: string,
- services: string[],
- retention: number,
- schedule: string,
- name?: string,
- resourceId?: string,
- enabled?: boolean
- ): Promise {
- if (typeof policyId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "policyId"');
- }
- if (typeof services === 'undefined') {
- throw new AppwriteException('Missing required parameter: "services"');
- }
- if (typeof retention === 'undefined') {
- throw new AppwriteException('Missing required parameter: "retention"');
- }
- if (typeof schedule === 'undefined') {
- throw new AppwriteException('Missing required parameter: "schedule"');
- }
- const apiPath = '/backups/policies';
- const payload: Payload = {};
- if (typeof policyId !== 'undefined') {
- payload['policyId'] = policyId;
- }
- if (typeof name !== 'undefined') {
- payload['name'] = name;
- }
- if (typeof services !== 'undefined') {
- payload['services'] = services;
- }
- if (typeof resourceId !== 'undefined') {
- payload['resourceId'] = resourceId;
- }
- if (typeof enabled !== 'undefined') {
- payload['enabled'] = enabled;
- }
- if (typeof retention !== 'undefined') {
- payload['retention'] = retention;
- }
- if (typeof schedule !== 'undefined') {
- payload['schedule'] = schedule;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('post', uri, apiHeaders, payload);
- }
-
- async getPolicy(policyId: string): Promise {
- if (typeof policyId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "policyId"');
- }
- const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
- const payload: Payload = {};
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-
- async updatePolicy(
- policyId: string,
- name?: string,
- retention?: number,
- schedule?: string,
- enabled?: boolean
- ): Promise {
- if (typeof policyId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "policyId"');
- }
- const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
- const payload: Payload = {};
- if (typeof name !== 'undefined') {
- payload['name'] = name;
- }
- if (typeof retention !== 'undefined') {
- payload['retention'] = retention;
- }
- if (typeof schedule !== 'undefined') {
- payload['schedule'] = schedule;
- }
- if (typeof enabled !== 'undefined') {
- payload['enabled'] = enabled;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('patch', uri, apiHeaders, payload);
- }
-
- async deletePolicy(policyId: string): Promise {
- if (typeof policyId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "policyId"');
- }
- const apiPath = '/backups/policies/{policyId}'.replace('{policyId}', policyId);
- const payload: Payload = {};
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('delete', uri, apiHeaders, payload);
- }
-
- async createRestoration(
- archiveId: string,
- services: string[],
- newResourceId?: string,
- newResourceName?: string
- ): Promise {
- if (typeof archiveId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "archiveId"');
- }
- if (typeof services === 'undefined') {
- throw new AppwriteException('Missing required parameter: "services"');
- }
- const apiPath = '/backups/restoration';
- const payload: Payload = {};
- if (typeof archiveId !== 'undefined') {
- payload['archiveId'] = archiveId;
- }
- if (typeof services !== 'undefined') {
- payload['services'] = services;
- }
- if (typeof newResourceId !== 'undefined') {
- payload['newResourceId'] = newResourceId;
- }
- if (typeof newResourceName !== 'undefined') {
- payload['newResourceName'] = newResourceName;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('post', uri, apiHeaders, payload);
- }
-
- async listRestorations(queries?: string[]): Promise {
- const apiPath = '/backups/restorations';
- const payload: Payload = {};
- if (typeof queries !== 'undefined') {
- payload['queries'] = queries;
- }
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-
- async getRestoration(restorationId: string): Promise {
- if (typeof restorationId === 'undefined') {
- throw new AppwriteException('Missing required parameter: "restorationId"');
- }
- const apiPath = '/backups/restorations/{restorationId}'.replace(
- '{restorationId}',
- restorationId
- );
- const payload: Payload = {};
- const uri = new URL(this.client.config.endpoint + apiPath);
-
- const apiHeaders: { [header: string]: string } = {
- 'content-type': 'application/json'
- };
-
- return await this.client.call('get', uri, apiHeaders, payload);
- }
-}
diff --git a/src/lib/stores/billing.ts b/src/lib/stores/billing.ts
index 6db2db697..61608aa3c 100644
--- a/src/lib/stores/billing.ts
+++ b/src/lib/stores/billing.ts
@@ -8,7 +8,7 @@ import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
import newDevUpgradePro from '$lib/components/billing/alerts/newDevUpgradePro.svelte';
import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequired.svelte';
-import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
+
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import { cachedStore } from '$lib/helpers/cache';
import type { BillingPlansMap } from '$lib/sdk/billing';
@@ -529,28 +529,6 @@ export function checkForMarkedForDeletion(org: Models.Organization) {
}
}
-export const paymentMissingMandate = writable(null);
-
-export async function checkForMandate(org: Models.Organization) {
- const paymentId = org.paymentMethodId ?? org.backupPaymentMethodId;
- if (!paymentId) return;
-
- const paymentMethod = await sdk.forConsole.account.getPaymentMethod({
- paymentMethodId: paymentId
- });
-
- if (paymentMethod?.mandateId === null && paymentMethod?.country.toLowerCase() === 'in') {
- headerAlert.add({
- id: 'paymentMandate',
- component: PaymentMandate,
- show: true,
- importance: 8
- });
- activeHeaderAlert.set(headerAlert.get());
- paymentMissingMandate.set(paymentMethod);
- }
-}
-
export async function checkForMissingPaymentMethod() {
const orgs = await sdk.forConsole.organizations.list({
queries: [
diff --git a/src/lib/stores/database.ts b/src/lib/stores/database.ts
index 4c93859ce..648708bd0 100644
--- a/src/lib/stores/database.ts
+++ b/src/lib/stores/database.ts
@@ -27,9 +27,9 @@ export async function checkForDatabaseBackupPolicies(
if (isCloud && backupsEnabled) {
try {
- const policies = await sdk
- .forProject(region, projectId)
- .backups.listPolicies([Query.limit(1), Query.equal('resourceId', database.$id)]);
+ const policies = await sdk.forProject(region, projectId).backups.listPolicies({
+ queries: [Query.limit(1), Query.equal('resourceId', database.$id)]
+ });
total = policies.total;
} catch (e) {
diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts
index 44a1777f4..22df4b17b 100644
--- a/src/lib/stores/oauth-providers.ts
+++ b/src/lib/stores/oauth-providers.ts
@@ -27,7 +27,7 @@ export const oAuthProviders: Record = {
apple: {
name: 'Apple',
icon: 'apple',
- docs: 'https://developer.apple.com/',
+ docs: 'https://developer.apple.com/sign-in-with-apple/',
component: Apple
},
auth0: {
diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts
index 38cf83b7f..efea17959 100644
--- a/src/lib/stores/sdk.ts
+++ b/src/lib/stores/sdk.ts
@@ -3,6 +3,7 @@ import {
Account,
Assistant,
Avatars,
+ Backups,
Client,
Console,
Functions,
@@ -25,7 +26,6 @@ import {
Realtime,
Organizations
} from '@appwrite.io/console';
-import { Backups } from '../sdk/backups';
import { Sources } from '$lib/sdk/sources';
import {
REGION_FRA,
diff --git a/src/lib/stores/uploader.ts b/src/lib/stores/uploader.ts
index ef848232c..8a04eae8a 100644
--- a/src/lib/stores/uploader.ts
+++ b/src/lib/stores/uploader.ts
@@ -1,4 +1,4 @@
-import { Client, ID, type Models, Sites, Storage } from '@appwrite.io/console';
+import { Client, Functions, ID, type Models, Sites, Storage } from '@appwrite.io/console';
import { writable } from 'svelte/store';
import { getApiEndpoint } from '$lib/stores/sdk';
import { page } from '$app/state';
@@ -32,6 +32,13 @@ const temporarySites = (region: string, projectId: string) => {
return new Sites(clientProject);
};
+const temporaryFunctions = (region: string, projectId: string) => {
+ const clientProject = new Client().setMode('admin');
+ const endpoint = getApiEndpoint(region);
+ clientProject.setEndpoint(endpoint).setProject(projectId);
+ return new Functions(clientProject);
+};
+
const createUploader = () => {
const { subscribe, set, update } = writable({
isOpen: false,
@@ -110,7 +117,19 @@ const createUploader = () => {
newFile.status = 'success';
updateFile(newFile.$id, newFile);
},
- uploadSiteDeployment: async (siteId: string, code: File) => {
+ uploadSiteDeployment: async ({
+ siteId,
+ code,
+ buildCommand,
+ installCommand,
+ outputDirectory
+ }: {
+ siteId: string;
+ code: File;
+ buildCommand?: string;
+ installCommand?: string;
+ outputDirectory?: string;
+ }) => {
const newDeployment: UploaderFile = {
$id: '',
resourceId: siteId,
@@ -132,6 +151,49 @@ const createUploader = () => {
siteId,
code,
activate: true,
+ buildCommand,
+ installCommand,
+ outputDirectory,
+ onProgress: (progress) => {
+ newDeployment.$id = progress.$id;
+ newDeployment.progress = progress.progress;
+ newDeployment.status = progress.progress === 100 ? 'success' : 'pending';
+ updateFile(progress.$id, newDeployment);
+ }
+ });
+ newDeployment.$id = uploadedFile.$id;
+ newDeployment.progress = 100;
+ newDeployment.status = 'success';
+ updateFile(newDeployment.$id, newDeployment);
+ },
+ uploadFunctionDeployment: async ({
+ functionId,
+ code
+ }: {
+ functionId: string;
+ code: File;
+ }) => {
+ const newDeployment: UploaderFile = {
+ $id: '',
+ resourceId: functionId,
+ name: code.name,
+ size: code.size,
+ progress: 0,
+ status: 'pending'
+ };
+ update((n) => {
+ n.isOpen = true;
+ n.isCollapsed = false;
+ n.files.unshift(newDeployment);
+ return n;
+ });
+ const uploadedFile = await temporaryFunctions(
+ page.params.region,
+ page.params.project
+ ).createDeployment({
+ functionId,
+ code,
+ activate: true,
onProgress: (progress) => {
newDeployment.$id = progress.$id;
newDeployment.progress = progress.progress;
diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte
index 196d56b8a..61feb71e8 100644
--- a/src/routes/(console)/+layout.svelte
+++ b/src/routes/(console)/+layout.svelte
@@ -15,7 +15,6 @@
import {
calculateTrialDay,
checkForEnterpriseTrial,
- checkForMandate,
checkForMarkedForDeletion,
checkForMissingPaymentMethod,
checkForNewDevUpgradePro,
@@ -308,7 +307,6 @@
if (org?.billingPlan !== BillingPlan.FREE) {
await paymentExpired(org);
await checkPaymentAuthorizationRequired(org);
- await checkForMandate(org);
if (org?.billingTrialDays) {
calculateTrialDay(org);
@@ -334,7 +332,6 @@
{
})
: null,
fetch(`${endpoint}/health/version`, {
- headers: { 'X-Appwrite-Project': project }
+ headers: { 'X-Appwrite-Project': project as string }
}).then((response) => response.json() as { version?: string }),
sdk.forConsole.console.variables()
]);
diff --git a/src/routes/(console)/account/identities.svelte b/src/routes/(console)/account/identities.svelte
index 924d22621..c326fdc1a 100644
--- a/src/routes/(console)/account/identities.svelte
+++ b/src/routes/(console)/account/identities.svelte
@@ -5,7 +5,6 @@
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { identities } from './store';
- import { toLocaleDateTime } from '$lib/helpers/date';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { oAuthProviders } from '$lib/stores/oauth-providers';
@@ -82,7 +81,13 @@
- {toLocaleDateTime(identity.providerAccessTokenExpiry)}
+ {#if identity.providerAccessTokenExpiry}
+
+ {:else}
+ -
+ {/if}
deleteIdentity(identity.$id)}>
diff --git a/src/routes/(console)/account/payments/editPaymentModal.svelte b/src/routes/(console)/account/payments/editPaymentModal.svelte
index fd63c1e1d..eb6dedcc9 100644
--- a/src/routes/(console)/account/payments/editPaymentModal.svelte
+++ b/src/routes/(console)/account/payments/editPaymentModal.svelte
@@ -9,15 +9,28 @@
import { Alert } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
- export let show = false;
- export let isLinked = false;
- export let selectedPaymentMethod: Models.PaymentMethod;
+ let {
+ show = $bindable(false),
+ isLinked = false,
+ selectedPaymentMethod
+ }: {
+ show: boolean;
+ isLinked?: boolean;
+ selectedPaymentMethod: Models.PaymentMethod;
+ } = $props();
+
+ let year: number | null = $state(null);
+ let month: string | null = $state(null);
+ let error: string | null = $state(null);
const currentYear = new Date().getFullYear();
- let error: string;
- let month: string;
- let year: number;
+ const months = Array.from({ length: 12 }, (_, i) => {
+ const value = String(i + 1).padStart(2, '0');
+ return { value, label: value };
+ });
+
+ const options = $derived(createMonthOptions(year));
async function handleSubmit() {
try {
@@ -30,6 +43,8 @@
trackEvent(Submit.PaymentMethodUpdate);
invalidate(Dependencies.PAYMENT_METHODS);
show = false;
+ trackEvent(Submit.PaymentMethodUpdate);
+ await invalidate(Dependencies.PAYMENT_METHODS);
addNotification({
type: 'success',
message: 'Your payment method has been updated'
@@ -41,20 +56,6 @@
}
function createMonthOptions(year: number) {
- const months = [
- { value: '01', label: '01' },
- { value: '02', label: '02' },
- { value: '03', label: '03' },
- { value: '04', label: '04' },
- { value: '05', label: '05' },
- { value: '06', label: '06' },
- { value: '07', label: '07' },
- { value: '08', label: '08' },
- { value: '09', label: '09' },
- { value: '10', label: '10' },
- { value: '11', label: '11' },
- { value: '12', label: '12' }
- ];
if (!year) return months;
if (year === currentYear) {
const currentMonth = new Date().getMonth() + 1;
@@ -63,8 +64,6 @@
return months;
}
}
-
- $: options = createMonthOptions(year);
diff --git a/src/routes/(console)/account/sessions/+page.svelte b/src/routes/(console)/account/sessions/+page.svelte
index eb0c51de4..bbc261abc 100644
--- a/src/routes/(console)/account/sessions/+page.svelte
+++ b/src/routes/(console)/account/sessions/+page.svelte
@@ -13,7 +13,14 @@
import type { PageData } from './$types';
import { addNotification } from '$lib/stores/notifications';
import { onMount } from 'svelte';
- import { Badge, Layout, Table, Typography, Icon } from '@appwrite.io/pink-svelte';
+ import {
+ Badge,
+ Layout,
+ Table,
+ Typography,
+ Icon,
+ InteractiveText
+ } from '@appwrite.io/pink-svelte';
import { IconGlobeAlt } from '@appwrite.io/pink-icons-svelte';
export let data: PageData;
@@ -92,9 +99,9 @@
@@ -146,7 +153,7 @@
{/if}
- {session.ip}
+
logout(session)}>Sign out
diff --git a/src/routes/(console)/organization-[organization]/+layout.ts b/src/routes/(console)/organization-[organization]/+layout.ts
index 7b63473f1..63f15ce7e 100644
--- a/src/routes/(console)/organization-[organization]/+layout.ts
+++ b/src/routes/(console)/organization-[organization]/+layout.ts
@@ -22,7 +22,7 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
depends(Dependencies.MEMBERS);
depends(Dependencies.PAYMENT_METHODS);
- const requestedOrg = await checkPlatformAndRedirect(params, organizations, prefs);
+ await checkPlatformAndRedirect(params, organizations, prefs);
let roles = isCloud ? [] : defaultRoles;
let scopes = isCloud ? [] : defaultScopes;
@@ -49,15 +49,10 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
sdk.forConsole.account.updatePrefs({ prefs: newPrefs });
}
- // fetch org only if we haven't already fetched it for platform check
- const orgPromise: Promise = requestedOrg
- ? Promise.resolve(requestedOrg)
- : (sdk.forConsole.teams.get({
- teamId: params.organization
- }) as Promise);
-
const [org, members, countryList, locale] = await Promise.all([
- orgPromise,
+ sdk.forConsole.teams.get({
+ teamId: params.organization
+ }) as Promise,
sdk.forConsole.teams.listMemberships({ teamId: params.organization }),
sdk.forConsole.locale.listCountries(),
sdk.forConsole.locale.get(),
@@ -101,7 +96,7 @@ async function checkPlatformAndRedirect(
params: { organization: string },
organizations: { teams: Array<{ $id: string; platform?: string }> },
prefs: Record
-): Promise {
+) {
// check if preloaded
let requestedOrg = organizations.teams.find((team) => team.$id === params.organization) as
| Models.Organization
@@ -159,8 +154,4 @@ async function checkPlatformAndRedirect(
redirect(303, resolve('/(console)'));
}
}
-
- // send the org,
- // if already in the full list so we don't have to make another API request.
- return requestedOrg;
}
diff --git a/src/routes/(console)/organization-[organization]/+page.svelte b/src/routes/(console)/organization-[organization]/+page.svelte
index 4794d8659..37ef03204 100644
--- a/src/routes/(console)/organization-[organization]/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/+page.svelte
@@ -71,12 +71,6 @@
return getServiceLimit('projects', null, data.currentPlan);
});
- const projectsToArchive = $derived.by(() => {
- return isCloud
- ? data.projects.projects.filter((project) => project.status === 'archived')
- : [];
- });
-
function filterPlatforms(platforms: { name: string; icon: string }[]) {
return platforms.filter(
(value, index, self) => index === self.findIndex((t) => t.name === value.name)
@@ -136,6 +130,19 @@
return project.status === 'archived';
}
+ const projectsToArchive = $derived(
+ (data.archivedProjectsPage ?? data.projects.projects).filter(
+ (project) => project.status === 'archived'
+ )
+ );
+
+ const activeTotalOverall = $derived(
+ data?.activeTotalOverall ??
+ data?.organization?.projects?.length ??
+ data?.projects?.total ??
+ 0
+ );
+
function clearSearch() {
searchQuery?.clearInput();
}
@@ -163,7 +170,7 @@
-
+
{#if $canWriteProjects}
{#if projectCreationDisabled && reachedProjectLimit}
@@ -238,7 +245,7 @@
{#if data.projects.total > 0}
{#each data.projects.projects as project}
@@ -323,13 +330,16 @@
name="Projects"
limit={data.limit}
offset={data.offset}
- total={data.projects.total} />
+ total={activeTotalOverall} />
+ currentPlan={$currentPlan}
+ archivedTotalOverall={data.archivedTotalOverall}
+ archivedOffset={data.archivedOffset}
+ limit={data.limit} />
diff --git a/src/routes/(console)/organization-[organization]/+page.ts b/src/routes/(console)/organization-[organization]/+page.ts
index dfd1d75fa..fc4256b03 100644
--- a/src/routes/(console)/organization-[organization]/+page.ts
+++ b/src/routes/(console)/organization-[organization]/+page.ts
@@ -1,11 +1,11 @@
import { Query } from '@appwrite.io/console';
+import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, getSearch, pageToOffset } from '$lib/helpers/load';
import { CARD_LIMIT, Dependencies } from '$lib/constants';
import type { PageLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
-import { isCloud } from '$lib/system';
export const load: PageLoad = async ({ params, url, route, depends, parent }) => {
const { scopes } = await parent();
@@ -20,26 +20,76 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
const offset = pageToOffset(page, limit);
const search = getSearch(url);
- const projects = await sdk.forConsole.projects.list({
- queries: [
- Query.offset(offset),
- Query.equal('teamId', params.organization),
- Query.limit(limit),
- Query.orderDesc(''),
- Query.select(['$id', 'name', 'platforms', 'region', ...(isCloud ? ['status'] : [])])
- ],
- search: search || undefined
- });
+ const archivedPageRaw = parseInt(url.searchParams.get('archivedPage') || '1', 10);
+ const archivedPage =
+ Number.isFinite(archivedPageRaw) && archivedPageRaw > 0 ? archivedPageRaw : 1;
+ const archivedOffset = pageToOffset(archivedPage, limit);
+
+ const searchQueries = search
+ ? [Query.or([Query.search('search', search), Query.contains('labels', search)])]
+ : [];
+ const commonQueries = [Query.equal('teamId', params.organization)];
+ const activeQueries = isCloud
+ ? [Query.or([Query.equal('status', 'active'), Query.isNull('status')])]
+ : [];
+
+ const [activeProjects, archivedProjects, activeTotal, archivedTotal] = await Promise.all([
+ sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(offset),
+ Query.limit(limit),
+ Query.orderDesc(''),
+ ...commonQueries,
+ ...searchQueries,
+ ...activeQueries
+ ]
+ }),
+ isCloud
+ ? sdk.forConsole.projects.list({
+ queries: [
+ Query.offset(archivedOffset),
+ Query.limit(limit),
+ Query.orderDesc(''),
+ ...commonQueries,
+ ...searchQueries,
+ Query.equal('status', 'archived')
+ ]
+ })
+ : Promise.resolve({ projects: [], total: 0 }),
+ sdk.forConsole.projects.list({
+ queries: [...commonQueries, ...activeQueries, ...searchQueries]
+ }),
+ isCloud
+ ? sdk.forConsole.projects.list({
+ queries: [...commonQueries, ...searchQueries, Query.equal('status', 'archived')]
+ })
+ : Promise.resolve({ projects: [], total: 0 })
+ ]);
// set `default` if no region!
- for (const project of projects.projects) {
+ for (const project of activeProjects.projects) {
project.region ??= 'default';
}
+ if (isCloud) {
+ for (const project of archivedProjects.projects) {
+ project.region ??= 'default';
+ }
+ }
return {
offset,
limit,
- projects,
+ projects: {
+ ...activeProjects,
+ projects: activeProjects.projects,
+ total: activeTotal.total
+ },
+ activeProjectsPage: activeProjects.projects,
+ archivedProjectsPage: archivedProjects.projects,
+ activeTotalOverall: activeTotal.total,
+ archivedTotalOverall: archivedTotal.total,
+ archivedOffset,
+ archivedPage,
search
};
};
diff --git a/src/routes/(console)/organization-[organization]/billing/+page.svelte b/src/routes/(console)/organization-[organization]/billing/+page.svelte
index 797584f7e..fdb29040c 100644
--- a/src/routes/(console)/organization-[organization]/billing/+page.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/+page.svelte
@@ -25,22 +25,15 @@
import { Alert } from '@appwrite.io/pink-svelte';
import { goto, invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
- import { base } from '$app/paths';
import type { PageData } from './$types';
+ import { resolve } from '$app/paths';
export let data: PageData;
- // Reactive statement to update organization when data changes
$: organization = data.organization;
-
- // why are these reactive?
- $: defaultPaymentMethod = data?.paymentMethods?.paymentMethods?.find(
- (method) => method.$id === organization?.paymentMethodId
- );
-
- $: backupPaymentMethod = data?.paymentMethods?.paymentMethods?.find(
- (method) => method.$id === organization?.backupPaymentMethodId
- );
+ $: baseUrl = resolve('/(console)/organization-[organization]/billing', {
+ organization: organization.$id
+ });
onMount(async () => {
if (page.url.searchParams.has('type')) {
@@ -62,7 +55,7 @@
organization.$id,
invoice.clientSecret,
organization.paymentMethodId,
- `${base}/organization-${organization.$id}/billing?type=validate-invoice&invoice=${invoice.$id}`
+ `${baseUrl}?type=validate-invoice&invoice=${invoice.$id}`
);
}
@@ -122,7 +115,7 @@
{/if}
{/if}
- {#if defaultPaymentMethod?.failed && !backupPaymentMethod}
+ {#if data.primaryPaymentMethod?.failed && !data.backupPaymentMethod}
@@ -156,7 +149,13 @@
currentInvoice={data?.billingInvoice} />
{/if}
-
+
+
+
{
depends(Dependencies.CREDIT);
depends(Dependencies.INVOICES);
depends(Dependencies.ADDRESS);
- //aggregation reloads on page param changes
+ // aggregation reloads on page param changes
depends(Dependencies.BILLING_AGGREGATION);
const billingAddressId = (organization as Models.Organization)?.billingAddressId;
@@ -93,6 +93,7 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => {
// make number
const credits = availableCredit ? availableCredit.available : null;
+ const { backup, primary } = getOrganizationPaymentMethods(organization, paymentMethods);
return {
paymentMethods,
@@ -109,6 +110,32 @@ export const load: PageLoad = async ({ parent, depends, url, route }) => {
offset: pageToOffset(
getPage(url) || 1,
getLimit(url, route, DEFAULT_BILLING_PROJECTS_LIMIT)
- )
+ ),
+
+ backupPaymentMethod: backup,
+ primaryPaymentMethod: primary
};
};
+
+function getOrganizationPaymentMethods(
+ organization: Models.Organization,
+ paymentMethods: Models.PaymentMethodList
+): {
+ backup: Models.PaymentMethod | null;
+ primary: Models.PaymentMethod | null;
+} {
+ let backup: Models.PaymentMethod | null = null;
+ let primary: Models.PaymentMethod | null = null;
+
+ for (const paymentMethod of paymentMethods.paymentMethods) {
+ if (paymentMethod.$id === organization.paymentMethodId) {
+ primary = paymentMethod;
+ } else if (paymentMethod.$id === organization.backupPaymentMethodId) {
+ backup = paymentMethod;
+ }
+
+ if (primary && backup) break;
+ }
+
+ return { primary, backup };
+}
diff --git a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
index 014cf9fc5..58164982e 100644
--- a/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/deleteOrgPayment.svelte
@@ -27,7 +27,7 @@
message: `The payment method has been removed from ${$organization.name}`
});
trackEvent(Submit.OrganizationPaymentDelete);
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
showDelete = false;
} catch (e) {
error = e.message;
@@ -50,7 +50,7 @@
message: `The payment method has been removed from ${$organization.name}`
});
trackEvent(Submit.OrganizationBackupPaymentDelete);
- await invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
showDelete = false;
} catch (e) {
error = e.message;
@@ -70,10 +70,10 @@
{:else}
+ title="Remove payment method"
+ onSubmit={isBackup ? removeBackupMethod : removeDefaultMethod}>
Are you sure you want to remove the payment method from {$organization?.name} ?
diff --git a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
index 82f9df979..fb1eba319 100644
--- a/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/paymentMethods.svelte
@@ -35,14 +35,14 @@
export let organization: Models.Organization;
export let methods: Models.PaymentMethodList;
+ export let backupMethod: Models.PaymentMethod;
+ export let primaryMethod: Models.PaymentMethod;
let showEdit = false;
let showDelete = false;
let showPayment = false;
let showReplace = false;
let isSelectedBackup = false;
- let backupPaymentMethod: Models.PaymentMethod;
- let defaultPaymentMethod: Models.PaymentMethod;
async function addPaymentMethod(paymentMethodId: string) {
try {
@@ -55,7 +55,7 @@
message: `A new payment method has been added to ${organization.name}`
});
trackEvent(Submit.OrganizationPaymentUpdate);
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
} catch (error) {
addNotification({
type: 'error',
@@ -75,7 +75,7 @@
type: 'success',
message: `A new payment method has been added to ${organization.name}`
});
- invalidate(Dependencies.ORGANIZATION);
+ await invalidate(Dependencies.PAYMENT_METHODS);
} catch (error) {
addNotification({
type: 'error',
@@ -85,33 +85,15 @@
}
}
- $: if (organization?.backupPaymentMethodId) {
- sdk.forConsole.organizations
- .getPaymentMethod({
- organizationId: organization.$id,
- paymentMethodId: organization.backupPaymentMethodId
- })
- .then((res) => (backupPaymentMethod = res));
- }
-
- $: if (organization?.paymentMethodId) {
- sdk.forConsole.organizations
- .getPaymentMethod({
- organizationId: organization.$id,
- paymentMethodId: organization.paymentMethodId
- })
- .then((res) => (defaultPaymentMethod = res));
- }
-
$: if (!showReplace) {
isSelectedBackup = false;
}
$: hasPaymentError =
- defaultPaymentMethod?.lastError ||
- defaultPaymentMethod?.expired ||
- backupPaymentMethod?.lastError ||
- backupPaymentMethod?.expired;
+ primaryMethod?.lastError ||
+ primaryMethod?.expired ||
+ backupMethod?.lastError ||
+ backupMethod?.expired;
@@ -137,14 +119,14 @@
-
+
- {#if defaultPaymentMethod?.userId === $user?.$id}
+ {#if primaryMethod?.userId === $user?.$id}
{
@@ -176,14 +158,14 @@
{#if organization?.backupPaymentMethodId}
-
+
- {#if backupPaymentMethod?.userId === $user?.$id}
+ {#if backupMethod?.userId === $user?.$id}
{
@@ -323,8 +305,8 @@
{/if}
{#if showEdit && isCloud && hasStripePublicKey}
+ bind:show={showEdit}
+ selectedPaymentMethod={isSelectedBackup ? backupMethod : primaryMethod} />
{/if}
{#if isCloud && hasStripePublicKey}
diff --git a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
index edb71a016..847ebd016 100644
--- a/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
+++ b/src/routes/(console)/organization-[organization]/billing/replaceCard.svelte
@@ -12,17 +12,29 @@
import type { PaymentMethod as StripePaymentMethod } from '@stripe/stripe-js';
import type { Models } from '@appwrite.io/console';
- export let show = false;
- export let isBackup = false;
- export let organization: Models.Organization;
- export let methods: Models.PaymentMethodList;
+ let {
+ show = $bindable(false),
+ isBackup = false,
+ methods,
+ organization
+ }: {
+ show?: boolean;
+ isBackup?: boolean;
+ methods: Models.PaymentMethodList;
+ organization: Models.Organization;
+ } = $props();
- let name: string;
- let error: string = null;
- let selectedPaymentMethodId: string;
- let showState: boolean = false;
- let state: string = '';
- let paymentMethod: StripePaymentMethod | null = null;
+ let name: string | null = $state(null);
+ let error: string | null = $state(null);
+ let showState: boolean = $state(false);
+ let countryState: string | null = $state(null);
+ let paymentMethod: StripePaymentMethod | null = $state(null);
+ let selectedPaymentMethodId: string | null = $state(null);
+
+ const filteredMethods = $derived(methods?.paymentMethods.filter((method) => !!method?.last4));
+ const submitEvent = $derived(
+ isBackup ? Submit.OrganizationBackupPaymentUpdate : Submit.OrganizationPaymentUpdate
+ );
onMount(async () => {
if (!organization.paymentMethodId && !organization.backupPaymentMethodId) {
@@ -44,12 +56,12 @@
async function handleSubmit() {
try {
if (selectedPaymentMethodId === '$new') {
- if (showState && !state) {
+ if (showState && !countryState) {
throw Error('Please select a state');
}
let method: Models.PaymentMethod;
if (showState) {
- method = await setPaymentMethod(paymentMethod.id, name, state);
+ method = await setPaymentMethod(paymentMethod.id, name, countryState);
} else {
const card = await submitStripeCard(name, organization.$id);
if (card && Object.hasOwn(card, 'id')) {
@@ -68,21 +80,17 @@
? await addBackupPaymentMethod(selectedPaymentMethodId)
: await addPaymentMethod(selectedPaymentMethodId);
+ await invalidate(Dependencies.PAYMENT_METHODS);
+
addNotification({
type: 'success',
message: `Your ${isBackup ? 'backup' : 'default'} payment method has been updated`
});
- invalidate(Dependencies.ORGANIZATION);
- trackEvent(
- isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
- );
+ trackEvent(submitEvent);
show = false;
- } catch (e) {
- error = e.message;
- trackError(
- e,
- isBackup ? Submit.OrganizationBackupPaymentDelete : Submit.OrganizationPaymentDelete
- );
+ } catch (err) {
+ error = err.message;
+ trackError(err, submitEvent);
}
}
@@ -92,8 +100,8 @@
organizationId: organization.$id,
paymentMethodId
});
- } catch (e) {
- error = e.message;
+ } catch (err) {
+ error = err.message;
}
}
@@ -103,23 +111,21 @@
organizationId: organization.$id,
paymentMethodId
});
- } catch (e) {
- error = e.message;
+ } catch (err) {
+ error = err.message;
}
}
-
- $: filteredMethods = methods?.paymentMethods.filter((method) => !!method?.last4);
Replace the existing payment method for your organization.
.layout-level-progress-bars {
gap: 1rem;
- z-index: 2;
+ z-index: 100;
display: flex;
flex-direction: column;
diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte
index 391274650..e8b9c7a29 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte
@@ -9,6 +9,7 @@
import {
AvatarInitials,
Copy,
+ type DeleteOperation,
type DeleteOperationState,
Empty,
EmptySearch,
@@ -60,20 +61,22 @@
);
}
- async function handleDelete(selectedRows: string[]): Promise {
- const promises = selectedRows.map((userId) => {
- return sdk.forProject(page.params.region, page.params.project).users.delete({ userId });
- });
+ async function handleDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((userId) =>
+ sdk.forProject(page.params.region, page.params.project).users.delete({ userId })
+ );
try {
- await Promise.all(promises);
- trackEvent(Submit.UserDelete, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.UserDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.UserDelete);
+ } else {
+ trackEvent(Submit.UserDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.USERS);
}
+
+ return result;
}
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte
index 48718cba3..385cfe1f5 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte
@@ -1,26 +1,23 @@
-
-
-
-
-
+
+
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte
new file mode 100644
index 000000000..22898b060
--- /dev/null
+++ b/src/routes/(console)/project-[region]-[project]/auth/security/passwordPolicies.svelte
@@ -0,0 +1,159 @@
+
+
+
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
new file mode 100644
index 000000000..6babbbecc
--- /dev/null
+++ b/src/routes/(console)/project-[region]-[project]/auth/security/sessionSecurity.svelte
@@ -0,0 +1,92 @@
+
+
+
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordDictionary.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordDictionary.svelte
deleted file mode 100644
index 0e921ab04..000000000
--- a/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordDictionary.svelte
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordHistory.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordHistory.svelte
deleted file mode 100644
index 0b4218487..000000000
--- a/src/routes/(console)/project-[region]-[project]/auth/security/updatePasswordHistory.svelte
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updatePersonalDataCheck.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updatePersonalDataCheck.svelte
deleted file mode 100644
index 71f87c24d..000000000
--- a/src/routes/(console)/project-[region]-[project]/auth/security/updatePersonalDataCheck.svelte
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionAlerts.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionAlerts.svelte
deleted file mode 100644
index ba8af9ec9..000000000
--- a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionAlerts.svelte
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionInvalidation.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionInvalidation.svelte
deleted file mode 100644
index c2c449e3e..000000000
--- a/src/routes/(console)/project-[region]-[project]/auth/security/updateSessionInvalidation.svelte
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte
index 18b2eb2d9..8a58aec9a 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte
@@ -12,6 +12,7 @@
SearchQuery,
PaginationWithLimit,
type DeleteOperationState,
+ type DeleteOperation,
MultiSelectionTable
} from '$lib/components';
import Create from '../createTeam.svelte';
@@ -44,20 +45,22 @@
);
};
- async function handleDelete(selectedRows: string[]): Promise {
- const promises = selectedRows.map((teamId) => {
- return sdk.forProject(page.params.region, page.params.project).teams.delete({ teamId });
- });
+ async function handleDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((teamId) =>
+ sdk.forProject(page.params.region, page.params.project).teams.delete({ teamId })
+ );
try {
- await Promise.all(promises);
- trackEvent(Submit.TeamDelete, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.TeamDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.TeamDelete);
+ } else {
+ trackEvent(Submit.TeamDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.TEAMS);
}
+
+ return result;
}
diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte
index 98999a305..d9368560f 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte
@@ -6,6 +6,7 @@
AvatarInitials,
PaginationWithLimit,
MultiSelectionTable,
+ type DeleteOperation,
type DeleteOperationState
} from '$lib/components';
import { Button } from '$lib/elements/forms';
@@ -29,23 +30,25 @@
let showDelete = $state(false);
let selectedMembership: Models.Membership | null = $state(null);
- async function handleBulkDelete(selectedRows: string[]): Promise {
- const promises = selectedRows.map((membershipId) => {
- return sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({
+ async function handleBulkDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((membershipId) =>
+ sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({
teamId: page.params.team,
membershipId
- });
- });
+ })
+ );
try {
- await Promise.all(promises);
- trackEvent(Submit.MembershipUpdate, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.MembershipUpdate);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.MembershipUpdate);
+ } else {
+ trackEvent(Submit.MembershipUpdate, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.MEMBERSHIPS);
}
+
+ return result;
}
diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte
index 5d9e956af..e1f053b95 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte
@@ -1,5 +1,10 @@
diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte
index 0c0b5525b..6f2b29355 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte
@@ -4,6 +4,7 @@
import {
AvatarInitials,
type DeleteOperationState,
+ type DeleteOperation,
MultiSelectionTable
} from '$lib/components';
import { Button } from '$lib/elements/forms';
@@ -23,14 +24,14 @@
let showDelete = $state(false);
let selectedMembership: Models.Membership | null = $state(null);
- async function handleBulkDelete(selectedRows: string[]): Promise {
+ async function handleBulkDelete(batchDelete: DeleteOperation): Promise {
// Precompute a lookup map from membershipId to teamId for efficient access
const membershipIdToTeamId: Record = {};
for (const membership of data.memberships.memberships) {
membershipIdToTeamId[membership.$id] = membership.teamId;
}
- const promises = selectedRows.map((membershipId) =>
+ const result = await batchDelete((membershipId) =>
sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({
teamId: membershipIdToTeamId[membershipId] || '',
membershipId
@@ -38,14 +39,16 @@
);
try {
- await Promise.all(promises);
- trackEvent(Submit.MembershipUpdate, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.MembershipUpdate);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.MembershipUpdate);
+ } else {
+ trackEvent(Submit.MembershipUpdate, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.MEMBERSHIPS);
}
+
+ return result;
}
diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/sessions/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/sessions/+page.svelte
index b7def9138..77feda0fb 100644
--- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/sessions/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/sessions/+page.svelte
@@ -1,6 +1,13 @@
diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
index e95c01cca..adba24b05 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte
@@ -14,9 +14,11 @@
import Table from './table.svelte';
import { registerCommands } from '$lib/commandCenter';
import { canWriteDatabases } from '$lib/stores/roles';
- import { Icon } from '@appwrite.io/pink-svelte';
+ import { Icon, Tooltip } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import EmptySearch from '$lib/components/emptySearch.svelte';
+ import { isServiceLimited } from '$lib/stores/billing';
+ import { organization } from '$lib/stores/organization';
export let data: PageData;
@@ -30,6 +32,8 @@
);
}
+ $: isLimited = isServiceLimited('databases', $organization?.billingPlan, data.databases.total);
+
$: $registerCommands([
{
label: 'Create database',
@@ -37,7 +41,7 @@
showCreate = true;
},
keys: ['c'],
- disabled: showCreate || !$canWriteDatabases,
+ disabled: showCreate || !$canWriteDatabases || isLimited,
icon: IconPlus,
group: 'databases',
rank: 10
@@ -52,10 +56,20 @@
bind:view={data.view}
searchPlaceholder="Search by name or ID">
{#if $canWriteDatabases}
- (showCreate = true)}>
-
- Create database
-
+
+
+ (showCreate = true)}>
+
+ Create database
+
+
+
+ You have reached the maximum number of databases for your plan.
+
+
{/if}
@@ -63,7 +77,11 @@
{#if data.view === 'grid'}
{:else}
-
+
{/if}
, policies: Record;
+ let lastBackups: Record = {};
+ let policies: Record = {};
if (isCloud && backupsEnabled) {
[policies, lastBackups] = await Promise.all([
@@ -85,19 +85,21 @@ async function fetchDatabasesAndBackups(
async function fetchPolicies(databases: Models.DatabaseList, params: RouteParams) {
if (isSelfHosted) return {};
- const databasePolicies: Record = {};
+ const databasePolicies: Record = {};
await Promise.all(
databases.databases.map(async (database) => {
try {
const { policies } = await sdk
.forProject(params.region, params.project)
- .backups.listPolicies([
- // TODO: are all needed!?
- // Query.limit(3),
- Query.equal('resourceType', 'database'),
- Query.equal('resourceId', database.$id)
- ]);
+ .backups.listPolicies({
+ queries: [
+ // TODO: are all needed!?
+ // Query.limit(3),
+ Query.equal('resourceType', 'database'),
+ Query.equal('resourceId', database.$id)
+ ]
+ });
if (policies.length > 0) {
databasePolicies[database.$id] = policies;
@@ -121,12 +123,14 @@ async function fetchLastBackups(databases: Models.DatabaseList, params: RoutePar
try {
const { archives } = await sdk
.forProject(params.region, params.project)
- .backups.listArchives([
- Query.limit(1),
- Query.orderDesc('$createdAt'),
- Query.equal('resourceType', 'database'),
- Query.equal('resourceId', database.$id)
- ]);
+ .backups.listArchives({
+ queries: [
+ Query.limit(1),
+ Query.orderDesc('$createdAt'),
+ Query.equal('resourceType', 'database'),
+ Query.equal('resourceId', database.$id)
+ ]
+ });
if (archives.length > 0) {
lastBackups[database.$id] = timeFromNow(archives[0].$createdAt);
diff --git a/src/routes/(console)/project-[region]-[project]/databases/create.svelte b/src/routes/(console)/project-[region]-[project]/databases/create.svelte
index f00b68e59..3fc1736dd 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/create.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/create.svelte
@@ -61,16 +61,14 @@
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
- return sdk
- .forProject(page.params.region, page.params.project)
- .backups.createPolicy(
- ID.unique(),
- ['databases'],
- policy.retained,
- policy.schedule,
- policy.label,
- resourceId
- );
+ return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
+ policyId: ID.unique(),
+ services: ['databases'],
+ retention: policy.retained,
+ schedule: policy.schedule,
+ name: policy.label,
+ resourceId
+ });
});
await Promise.all(totalPoliciesPromise);
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte
index 99521078d..e64490541 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.svelte
@@ -66,9 +66,10 @@
const createManualBackup = async () => {
try {
- await sdk
- .forProject(page.params.region, page.params.project)
- .backups.createArchive(['databases'], data.database.$id);
+ await sdk.forProject(page.params.region, page.params.project).backups.createArchive({
+ services: ['databases'],
+ resourceId: data.database.$id
+ });
await invalidate(Dependencies.BACKUPS);
addNotification({
type: 'success',
@@ -119,16 +120,14 @@
const totalPoliciesPromise = totalPolicies.map((policy) => {
cronExpression(policy);
- return sdk
- .forProject(page.params.region, page.params.project)
- .backups.createPolicy(
- ID.unique(),
- ['databases'],
- policy.retained,
- policy.schedule,
- policy.label,
- data.database.$id
- );
+ return sdk.forProject(page.params.region, page.params.project).backups.createPolicy({
+ policyId: ID.unique(),
+ services: ['databases'],
+ retention: policy.retained,
+ schedule: policy.schedule,
+ name: policy.label,
+ resourceId: data.database.$id
+ });
});
try {
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.ts
index 52d9e65c9..60d9bfb21 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/+page.ts
@@ -2,8 +2,8 @@ import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/loa
import { Dependencies, PAGE_LIMIT } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import { Query } from '@appwrite.io/console';
-import type { BackupArchive, BackupArchiveList, BackupPolicyList } from '$lib/sdk/backups';
import { isCloud } from '$lib/system';
+import { type Models } from '@appwrite.io/console';
export const load = async ({ params, url, route, depends, parent }) => {
depends(Dependencies.BACKUPS);
@@ -13,8 +13,8 @@ export const load = async ({ params, url, route, depends, parent }) => {
const view = getView(url, route, View.Grid);
const offset = pageToOffset(page, limit);
- let backups: BackupArchiveList = { total: 0, archives: [] };
- let policies: BackupPolicyList = { total: 0, policies: [] };
+ let backups: Models.BackupArchiveList = { total: 0, archives: [] };
+ let policies: Models.BackupPolicyList = { total: 0, policies: [] };
// already loaded by parent.
const { currentPlan } = await parent();
@@ -23,23 +23,23 @@ export const load = async ({ params, url, route, depends, parent }) => {
if (isCloud && backupsEnabled) {
try {
[backups, policies] = await Promise.all([
- sdk
- .forProject(params.region, params.project)
- .backups.listArchives([
+ sdk.forProject(params.region, params.project).backups.listArchives({
+ queries: [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
- ]),
+ ]
+ }),
- sdk
- .forProject(params.region, params.project)
- .backups.listPolicies([
+ sdk.forProject(params.region, params.project).backups.listPolicies({
+ queries: [
Query.orderDesc('$createdAt'),
Query.equal('resourceType', 'database'),
Query.equal('resourceId', params.database)
- ])
+ ]
+ })
]);
} catch (e) {
// ignore
@@ -59,17 +59,17 @@ export const load = async ({ params, url, route, depends, parent }) => {
};
};
-const groupArchivesByPolicy = (archives: BackupArchive[]) => {
+const groupArchivesByPolicy = (archives: Models.BackupArchive[]) => {
return archives.reduce((acc, archive) => {
if (!acc.has(archive.policyId)) {
acc.set(archive.policyId, []);
}
acc.get(archive.policyId)!.push(archive);
return acc;
- }, new Map());
+ }, new Map());
};
-const getLatestBackupForPolicies = (policyIdMap: Map) => {
+const getLatestBackupForPolicies = (policyIdMap: Map) => {
const latestBackups = new Map();
for (const [policyId, archives] of policyIdMap) {
const latestBackup = archives.sort(
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/policy.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/policy.svelte
index e4ccd8b91..f73fc68a9 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/policy.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/policy.svelte
@@ -11,7 +11,6 @@
import { toLocaleDateTime } from '$lib/helpers/date';
import EmptyDark from '$lib/images/backups/backups-dark.png';
import EmptyLight from '$lib/images/backups/backups-light.png';
- import type { BackupPolicy, BackupPolicyList } from '$lib/sdk/backups';
import { backupFrequencies } from '$lib/helpers/backups';
import { Click, trackEvent } from '$lib/actions/analytics';
import {
@@ -27,21 +26,22 @@
import { Confirm } from '$lib/components/index.js';
import Ellipse from './components/Ellipse.svelte';
import { page } from '$app/state';
+ import { type Models } from '@appwrite.io/console';
let showDelete = false;
- let selectedPolicy: BackupPolicy = null;
+ let selectedPolicy: Models.BackupPolicy = null;
let showEveryPolicy = false;
export let showCreatePolicy = false;
- export let policies: BackupPolicyList;
+ export let policies: Models.BackupPolicyList;
export let lastBackupDates: Record;
async function deletePolicy() {
try {
- await sdk
- .forProject(page.params.region, page.params.project)
- .backups.deletePolicy(selectedPolicy.$id);
+ await sdk.forProject(page.params.region, page.params.project).backups.deletePolicy({
+ policyId: selectedPolicy.$id
+ });
addNotification({
type: 'success',
message: 'Backup policy has been deleted'
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte
index d33593160..5cd807592 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte
@@ -3,6 +3,7 @@
Card,
Confirm,
type DeleteOperationState,
+ type DeleteOperation,
Modal,
MultiSelectionTable
} from '$lib/components';
@@ -14,11 +15,10 @@
import { addNotification } from '$lib/stores/notifications';
import { invalidate } from '$app/navigation';
import { calculateSize } from '$lib/helpers/sizeConvertion';
- import { ID } from '@appwrite.io/console';
+ import { ID, type Models } from '@appwrite.io/console';
import { columns } from './store';
import { database } from '../store';
- import type { BackupArchive, BackupPolicy } from '$lib/sdk/backups';
- import { Click, trackEvent } from '$lib/actions/analytics';
+ import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { copy } from '$lib/helpers/copy';
import { LabelCard } from '$lib/components/index.js';
import DualTimeView from '$lib/components/dualTimeView.svelte';
@@ -52,7 +52,7 @@
} = $props();
let showDelete = $state(false);
- let selectedBackup: BackupArchive | null = $state(null);
+ let selectedBackup: Models.BackupArchive | null = $state(null);
let showDropdown = [];
@@ -86,15 +86,15 @@
);
});
- function getPolicyDetails(policyId: string | null): BackupPolicy | null {
+ function getPolicyDetails(policyId: string | null): Models.BackupPolicy | null {
return data.policies.policies.find((policy) => policy.$id === policyId);
}
- function getCleanBackupName(backup: BackupArchive): string {
+ function getCleanBackupName(backup: Models.BackupArchive): string {
return toLocaleDateTime(backup.$createdAt).replaceAll(',', '');
}
- function getBackupStatus(backup: BackupArchive) {
+ function getBackupStatus(backup: Models.BackupArchive) {
switch (backup.status) {
case 'pending':
return 'pending';
@@ -110,39 +110,46 @@
}
}
- async function deleteBackups(selectedRows: string[]): Promise {
- const promises = selectedRows.map((archiveId) => {
- return sdk
+ async function deleteSingleBackup(archiveId: string) {
+ try {
+ await sdk
.forProject(page.params.region, page.params.project)
- .backups.deleteArchive(archiveId);
- });
+ .backups.deleteArchive({ archiveId });
+
+ addNotification({
+ type: 'success',
+ message: 'Backup deleted'
+ });
+
+ showDelete = false;
+ selectedBackup = null;
+ await invalidate(Dependencies.BACKUPS);
+ } catch (error) {
+ addNotification({
+ type: 'error',
+ message: error.message
+ });
+ }
+ }
+
+ async function deleteBackups(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((archiveId) =>
+ sdk
+ .forProject(page.params.region, page.params.project)
+ .backups.deleteArchive({ archiveId })
+ );
try {
- await Promise.all(promises);
-
- if (selectedBackup) {
- addNotification({
- type: 'success',
- message: '1 backup deleted'
- });
- }
- } catch (error) {
- if (selectedBackup) {
- addNotification({
- type: 'error',
- message: error.message
- });
+ if (result.error) {
+ trackError(result.error, Submit.DatabaseBackupDelete);
} else {
- return error;
+ trackEvent(Submit.DatabaseBackupDelete);
}
} finally {
- if (selectedBackup) {
- showDelete = false;
- selectedBackup = null;
- }
-
await invalidate(Dependencies.BACKUPS);
}
+
+ return result;
}
async function restoreBackup() {
@@ -154,12 +161,12 @@
try {
await sdk
.forProject(page.params.region, page.params.project)
- .backups.createRestoration(
- selectedBackup.$id,
- ['databases'],
- newDatabaseInfo.id ?? ID.unique(),
- newDatabaseInfo.name
- );
+ .backups.createRestoration({
+ archiveId: selectedBackup.$id,
+ services: ['databases'],
+ newResourceId: newDatabaseInfo.id ?? ID.unique(),
+ newResourceName: newDatabaseInfo.name
+ });
await invalidate(Dependencies.BACKUPS);
addNotification({
@@ -299,7 +306,7 @@
bind:open={showDelete}
onSubmit={async () => {
if (!selectedBackup) return;
- await deleteBackups([selectedBackup.$id]);
+ await deleteSingleBackup(selectedBackup.$id);
}}>
Are you sure you want to delete the {getCleanBackupName(selectedBackup)} backup?
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/float.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/float.svelte
index bf86bf0c3..3badf6d94 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/float.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/columns/float.svelte
@@ -86,7 +86,7 @@
label="Min"
placeholder="Enter size"
bind:value={data.min}
- step={0.1}
+ step="any"
{disabled}
required={editing} />
@@ -95,7 +95,7 @@
label="Max"
placeholder="Enter size"
bind:value={data.max}
- step={0.1}
+ step="any"
{disabled}
required={editing} />
@@ -109,7 +109,7 @@
bind:value={data.default}
disabled={data.required || data.array || disabled}
nullable={!data.required && !data.array}
- step={0.1} />
+ step="any" />
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte
index f790579c5..fccc94df4 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/columns/types/relationship.svelte
@@ -297,7 +297,7 @@
{
+ const success = await updateRowContents(row);
+ if (success) {
+ // database update succeeded!
+ paginatedRows.update(index, row);
+ }
+ return success;
+ }}
noInlineEdit={isRelatedToMany && hasItems}
onChange={(row) => paginatedRows.update(index, row)}
onRevert={(row) => paginatedRows.update(index, row)}
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts
index 6c6a28489..913bba1e3 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store.ts
@@ -193,7 +193,9 @@ export const expandTabs = writable(null);
export const spreadsheetRenderKey = writable('initial');
export const paginatedRowsLoading = writable(false);
-export const paginatedRows = createSparsePagedDataStore(SPREADSHEET_PAGE_LIMIT);
+export const paginatedRows = createSparsePagedDataStore(
+ SPREADSHEET_PAGE_LIMIT
+);
export const PROHIBITED_ROW_KEYS = [
'$id',
diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte
index fa19cde37..6a5c73352 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte
@@ -3,7 +3,12 @@
import { resolve } from '$app/paths';
import { page } from '$app/state';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
- import { Id, MultiSelectionTable, type DeleteOperationState } from '$lib/components';
+ import {
+ Id,
+ MultiSelectionTable,
+ type DeleteOperation,
+ type DeleteOperationState
+ } from '$lib/components';
import { Dependencies } from '$lib/constants';
import DualTimeView from '$lib/components/dualTimeView.svelte';
import { canWriteTables } from '$lib/stores/roles';
@@ -20,23 +25,26 @@
data: PageData;
} = $props();
- async function onDelete(selectedTables: string[]): Promise {
- const promises = selectedTables.map((tableId) =>
+ async function onDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((tableId) =>
sdk.forProject(page.params.region, page.params.project).tablesDB.deleteTable({
databaseId: page.params.database,
tableId
})
);
+
try {
- await Promise.all(promises);
- trackEvent(Submit.TableDelete);
- } catch (error) {
- trackError(error, Submit.TableDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.TableDelete);
+ } else {
+ trackEvent(Submit.TableDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.TABLES);
subNavigation.update();
}
+
+ return result;
}
function getTableHref(table: Models.Table) {
diff --git a/src/routes/(console)/project-[region]-[project]/databases/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/table.svelte
index 10ec6f307..0764bf9ea 100644
--- a/src/routes/(console)/project-[region]-[project]/databases/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/databases/table.svelte
@@ -2,14 +2,23 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import { Id } from '$lib/components';
- import { toLocaleDateTime } from '$lib/helpers/date';
+ import DualTimeView from '$lib/components/dualTimeView.svelte';
import { columns } from './store';
import { IconExclamation } from '@appwrite.io/pink-icons-svelte';
import { Layout, Tooltip, Table, Icon } from '@appwrite.io/pink-svelte';
- import type { BackupPolicy } from '$lib/sdk/backups';
+ import { type Models } from '@appwrite.io/console';
- export let data;
- const tables = data.tables;
+ let {
+ tables,
+ policies,
+ databases,
+ lastBackups
+ }: {
+ tables: Record;
+ databases: Models.DatabaseList;
+ lastBackups: Record;
+ policies: Record;
+ } = $props();
function getPolicyDescription(cron: string): string {
const [minute, hour, dayOfMonth, , dayOfWeek] = cron.split(' ');
@@ -19,6 +28,10 @@
if (minute !== '*' && hour === '*') return 'Hourly';
if (hour !== '*') return 'Daily';
}
+
+ function getPoliciesDescription(policies: Models.BackupPolicy[] | null): string {
+ return policies?.map((policy) => getPolicyDescription(policy.schedule)).join(', ') ?? '';
+ }
@@ -27,7 +40,7 @@
{title}
{/each}
- {#each data.databases.databases as database (database.$id)}
+ {#each databases.databases as database (database.$id)}
{@const tableId = tables[database?.$id] ?? null}
{@const tableHref = tableId ? `/table-${tableId}` : ''}
@@ -45,18 +58,16 @@
{:else if column.id === 'name'}
{database.name}
{:else if column.id === 'backup'}
- {@const policies = data.policies?.[database.$id] ?? null}
- {@const lastBackup = data.lastBackups?.[database.$id] ?? null}
- {@const description = policies
- ?.map((policy: BackupPolicy) => getPolicyDescription(policy.schedule))
- .join(', ')}
+ {@const backupPolicies = policies?.[database.$id] ?? null}
+ {@const lastBackup = lastBackups?.[database.$id] ?? null}
+ {@const description = getPoliciesDescription(backupPolicies)}
- {#if !policies}
+ {#if !backupPolicies}
+ {:else if column.type === 'datetime'}
+
{:else}
- {toLocaleDateTime(database[column.id])}
+ {database[column.id]}
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte
index e53837412..1a2b1ab93 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/create-function/manual/+page.svelte
@@ -1,6 +1,6 @@
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte
index 03e7f1dcf..acb5ece7a 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/(modals)/createManual.svelte
@@ -6,15 +6,14 @@
import { Button } from '$lib/elements/forms';
import { InvalidFileType, removeFile } from '$lib/helpers/files';
import { addNotification } from '$lib/stores/notifications';
- import { sdk } from '$lib/stores/sdk';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { Icon, Layout, Tooltip, Typography, Upload } from '@appwrite.io/pink-svelte';
import { func } from '../store';
- import { page } from '$app/state';
import { consoleVariables } from '$routes/(console)/store';
import { currentPlan } from '$lib/stores/organization';
import { isCloud } from '$lib/system';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
+ import { uploader } from '$lib/stores/uploader';
export let show = false;
@@ -30,17 +29,16 @@
async function create() {
try {
- await sdk
- .forProject(page.params.region, page.params.project)
- .functions.createDeployment({
- functionId: $func.$id,
- code: files[0],
- activate: true
- });
- await invalidate(Dependencies.DEPLOYMENTS);
- files = undefined;
+ uploader.uploadFunctionDeployment({
+ functionId: $func.$id,
+ code: files[0]
+ });
show = false;
- trackEvent(Submit.DeploymentCreate);
+ files = undefined;
+ await invalidate(Dependencies.DEPLOYMENTS);
+ trackEvent(Submit.DeploymentCreate, {
+ type: 'function'
+ });
addNotification({
type: 'success',
message: 'Deployment created successfully'
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte
index 9a9b47b2e..140524354 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/deployment-[deployment]/+page.svelte
@@ -41,11 +41,7 @@
let { data } = $props();
let effectiveStatus = $derived(
- getEffectiveBuildStatus(
- data.deployment.status,
- data.deployment.$createdAt,
- $regionalConsoleVariables
- )
+ getEffectiveBuildStatus(data.deployment, $regionalConsoleVariables)
);
let showDelete = $state(false);
let showCancel = $state(false);
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
index 4145bf828..5fdc16ceb 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/+page.ts
@@ -29,6 +29,7 @@ export const load: PageLoad = async ({ depends, params, url, route, parent }) =>
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
+ Query.orderDesc('$updatedAt'),
...parsedQueries.values()
],
search: search || undefined
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
index f741c03ee..5f36c6bc1 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/store.ts
@@ -15,5 +15,12 @@ export const columns = writable([
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
+ },
+
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
]);
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
index 98e29e5ba..83daf1ffe 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/domains/table.svelte
@@ -25,6 +25,7 @@
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
proxyRules,
@@ -46,6 +47,28 @@
? 'Deployed from ' + proxy.deploymentVcsProviderBranch
: 'Active deployment';
};
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -117,6 +140,15 @@
{:else if column.id === 'target'}
{proxyTarget(proxyRule)}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte
index f681c04ef..1e6915c38 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte
@@ -1,5 +1,10 @@
diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte
index 7b46acc09..a70bf52d5 100644
--- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte
@@ -1,5 +1,10 @@
@@ -87,8 +95,7 @@
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
- deployment.status,
- deployment.$createdAt,
+ deployment,
$regionalConsoleVariables
)}
{
- const promises = selectedRows.map((id) =>
+ async function handleDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete((id) =>
sdk
.forProject(page.params.region, page.params.project)
.messaging.delete({ messageId: id })
);
try {
- await Promise.all(promises);
- trackEvent(Submit.MessagingMessageDelete, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.MessagingMessageDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.MessagingMessageDelete);
+ } else {
+ trackEvent(Submit.MessagingMessageDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.MESSAGING_MESSAGES);
}
+
+ return result;
}
onMount(() => {
diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte
index a54178b3d..f7ed22281 100644
--- a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte
@@ -1,7 +1,12 @@
diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte
index f5525754e..709529b0f 100644
--- a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte
@@ -1,7 +1,12 @@
diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte
index c9a64e9af..113d9d76e 100644
--- a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte
@@ -2,7 +2,12 @@
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
- import { type DeleteOperationState, Id, MultiSelectionTable } from '$lib/components';
+ import {
+ type DeleteOperationState,
+ type DeleteOperation,
+ Id,
+ MultiSelectionTable
+ } from '$lib/components';
import { Dependencies } from '$lib/constants';
import type { PageData } from './$types';
import ProviderType from '../../providerType.svelte';
@@ -32,8 +37,8 @@
return record;
});
- async function handleDelete(selectedRows: string[]): Promise {
- async function deleteSubscriber(subscriberId: string) {
+ async function handleDelete(batchDelete: DeleteOperation): Promise {
+ const result = await batchDelete(async (subscriberId) => {
await sdk
.forProject(page.params.region, page.params.project)
.messaging.deleteSubscriber({
@@ -44,19 +49,19 @@
const { target } = subscribers[subscriberId];
const { [target.$id]: _, ...rest } = $targetsById;
$targetsById = rest;
- }
-
- const promises = selectedRows.map((id) => deleteSubscriber(id));
+ });
try {
- await Promise.all(promises);
- trackEvent(Submit.MessagingTopicSubscriberDelete, { total: selectedRows.length });
- } catch (error) {
- trackError(error, Submit.MessagingTopicSubscriberDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.MessagingTopicSubscriberDelete);
+ } else {
+ trackEvent(Submit.MessagingTopicSubscriberDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS);
}
+
+ return result;
}
diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte
index 9cfb56a83..535be9bd6 100644
--- a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte
@@ -67,7 +67,7 @@
allowSelection={$canWriteKeys}
showSuccessNotification={false}
resource={`${capitalize(label)} key`}
- onDelete={(selectedRows) => {
+ onDelete={(_, selectedRows) => {
showDeleteModal = true;
selectedKeys = selectedRows;
}}>
diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte
index 22d0bce77..de3cbcb2f 100644
--- a/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte
@@ -81,7 +81,11 @@
import { page } from '$app/state';
import type { PageProps } from './$types';
import type { Models } from '@appwrite.io/console';
- import { type DeleteOperationState, MultiSelectionTable } from '$lib/components';
+ import {
+ type DeleteOperationState,
+ type DeleteOperation,
+ MultiSelectionTable
+ } from '$lib/components';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError } from '$lib/actions/analytics';
import { invalidate } from '$app/navigation';
@@ -129,24 +133,26 @@
}
async function handlePlatformDelete(
- selectedPlatforms: string[]
+ batchDelete: DeleteOperation
): Promise {
- const promises = selectedPlatforms.map((platformId) => {
- return sdk.forConsole.projects.deletePlatform({
+ const result = await batchDelete((platformId) =>
+ sdk.forConsole.projects.deletePlatform({
projectId: page.params.project,
platformId
- });
- });
+ })
+ );
try {
- await Promise.all(promises);
- trackEvent(Submit.PlatformDelete, { total: selectedPlatforms.length });
- } catch (error) {
- trackError(error, Submit.PlatformDelete);
- return error;
+ if (result.error) {
+ trackError(result.error, Submit.PlatformDelete);
+ } else {
+ trackEvent(Submit.PlatformDelete, { total: result.deleted.length });
+ }
} finally {
await invalidate(Dependencies.PROJECT);
}
+
+ return result;
}
setOverviewAction(Action);
diff --git a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
index edf09b92f..8c427cc23 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/+page.svelte
@@ -15,6 +15,7 @@
import ChangeOrganization from './changeOrganization.svelte';
import UpdateVariables from '../updateVariables.svelte';
import { page } from '$app/state';
+ import UpdateLabels from './updateLabels.svelte';
export let data;
@@ -84,8 +85,9 @@
{#if $project}
-
{#if $canWriteProjects}
+
+
const { organization } = await parent();
const rules = await sdk.forProject(params.region, params.project).proxy.listRules({
- queries: [Query.equal('type', RuleType.API), Query.equal('trigger', RuleTrigger.MANUAL)],
+ queries: [
+ Query.equal('type', RuleType.API),
+ Query.equal('trigger', RuleTrigger.MANUAL),
+ Query.orderDesc('$updatedAt')
+ ],
search: search || undefined
});
diff --git a/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
index 594988a7f..051a20d3a 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte
@@ -24,6 +24,7 @@
import { regionalProtocol } from '../../store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
domains,
@@ -45,8 +46,36 @@
type: 'string',
format: 'string',
width: { min: 300, max: 550 }
+ },
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
];
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -116,6 +145,15 @@
{/if}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/settings/updateLabels.svelte b/src/routes/(console)/project-[region]-[project]/settings/updateLabels.svelte
new file mode 100644
index 000000000..21d324ec4
--- /dev/null
+++ b/src/routes/(console)/project-[region]-[project]/settings/updateLabels.svelte
@@ -0,0 +1,102 @@
+
+
+
diff --git a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte
index 4ee496dec..e7ec5e8d1 100644
--- a/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/settings/webhooks/+page.svelte
@@ -2,7 +2,7 @@
import { base } from '$app/paths';
import { page } from '$app/state';
import { Empty, Id } from '$lib/components';
- import { toLocaleDateTime } from '$lib/helpers/date';
+ import DualTimeView from '$lib/components/dualTimeView.svelte';
import { Container } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import type { PageData } from './$types';
@@ -58,7 +58,11 @@
{:else if column.id === 'events'}
{webhook.events.length}
{:else if column.type === 'datetime'}
- {webhook[column.id] ? toLocaleDateTime(webhook[column.id]) : '-'}
+ {#if webhook[column.id]}
+
+ {:else}
+ -
+ {/if}
{:else if column.id === 'enabled'}
{@const effectiveStatus = getEffectiveBuildStatus(
- deployment.status,
- deployment.$createdAt,
+ deployment,
$regionalConsoleVariables
)}
{#if !inCard}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/logs.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/logs.svelte
index 7fae6d0ab..51c407652 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/(components)/logs.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/logs.svelte
@@ -6,6 +6,7 @@
case 'ready':
return 'success';
case 'building':
+ case 'finalizing':
return 'warning';
case 'processing':
return undefined;
@@ -40,9 +41,7 @@
emptyCopy?: string;
} = $props();
- let effectiveStatus = $derived(
- getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
- );
+ let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
function setCopy() {
if (effectiveStatus === 'failed') {
@@ -73,7 +72,7 @@
variant="secondary"
type={badgeTypeDeployment(effectiveStatus)} />
-
+
{/if}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte
index 47bc28da3..79a00f541 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/logsTimer.svelte
@@ -7,15 +7,13 @@
import { Layout, Spinner, Typography } from '@appwrite.io/pink-svelte';
- let { status, deployment }: { status: string; deployment: Models.Deployment } = $props();
+ let { deployment }: { deployment: Models.Deployment } = $props();
- let effectiveStatus = $derived(
- getEffectiveBuildStatus(status, deployment.$createdAt, $regionalConsoleVariables)
- );
+ let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
- {#if ['processing', 'building'].includes(effectiveStatus)}
+ {#if ['processing', 'building', 'finalizing'].includes(effectiveStatus)}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte
index 78ad644f4..eb392282a 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/(components)/siteCard.svelte
@@ -42,9 +42,7 @@
footer?: Snippet;
} = $props();
- let effectiveStatus = $derived(
- getEffectiveBuildStatus(deployment.status, deployment.$createdAt, $regionalConsoleVariables)
- );
+ let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
let show = $state(false);
const totalSize = $derived(humanFileSize(deployment?.totalSize ?? 0));
diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploying/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploying/+page.svelte
index 3cb100543..b41104e09 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/create-site/deploying/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/deploying/+page.svelte
@@ -12,32 +12,76 @@
import { onMount } from 'svelte';
import { getFrameworkIcon } from '$lib/stores/sites';
import type { Models } from '@appwrite.io/console';
+ import { getEffectiveBuildStatus } from '$lib/helpers/buildTimeout';
+ import { regionalConsoleVariables } from '$routes/(console)/project-[region]-[project]/store';
let { data } = $props();
let deployment = $state(data.deployment);
+ let skipScreenshotTimeout: ReturnType | null = $state(null);
+
+ let effectiveStatus = $derived(getEffectiveBuildStatus(deployment, $regionalConsoleVariables));
onMount(() => {
- return realtime.forConsole(page.params.region, 'console', async (response) => {
- if (
- response.events.includes(
- `sites.${data.site.$id}.deployments.${data.deployment.$id}.update`
- )
- ) {
- deployment = response.payload as Models.Deployment;
- if (deployment.status === 'ready') {
- const resolvedUrl = resolve(
- '/(console)/project-[region]-[project]/sites/create-site/finish',
- {
- region: page.params.region,
- project: page.params.project
+ const timeoutCleanup = () => {
+ if (skipScreenshotTimeout) {
+ clearTimeout(skipScreenshotTimeout);
+ skipScreenshotTimeout = null;
+ }
+ };
+
+ const realtimeUnsubscribe = realtime.forConsole(
+ page.params.region,
+ 'console',
+ async (response) => {
+ if (
+ response.events.includes(
+ `sites.${data.site.$id}.deployments.${data.deployment.$id}.update`
+ )
+ ) {
+ deployment = response.payload as Models.Deployment;
+
+ const isReady = deployment.status === 'ready';
+
+ const isFinished =
+ isReady && deployment.screenshotLight && deployment.screenshotDark;
+
+ // Fallback mechanism
+ // If ready but not finished for over 30 seconds, go anyway
+ if (isReady && !skipScreenshotTimeout) {
+ skipScreenshotTimeout = setTimeout(async () => {
+ goToFinishScreen();
+ }, 30000);
+ }
+
+ if (isReady && isFinished) {
+ if (skipScreenshotTimeout) {
+ clearTimeout(skipScreenshotTimeout);
+ skipScreenshotTimeout = null;
}
- );
- await goto(`${resolvedUrl}?site=${data.site.$id}`);
+
+ goToFinishScreen();
+ }
}
}
- });
+ );
+
+ return () => {
+ realtimeUnsubscribe();
+ timeoutCleanup();
+ };
});
+
+ async function goToFinishScreen() {
+ const resolvedUrl = resolve(
+ '/(console)/project-[region]-[project]/sites/create-site/finish',
+ {
+ region: page.params.region,
+ project: page.params.project
+ }
+ );
+ await goto(`${resolvedUrl}?site=${data.site.$id}`);
+ }
- {#if ['processing', 'building'].includes(data.deployment.status)}
+ {#if ['processing', 'building', 'finalizing'].includes(effectiveStatus)}
Deployment will continue in the background
diff --git a/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte b/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte
index 8cecb4acc..5a4536527 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/create-site/manual/+page.svelte
@@ -1,6 +1,6 @@
@@ -74,8 +82,7 @@
{#snippet children(root)}
{#each data.deploymentList.deployments as deployment (deployment.$id)}
{@const effectiveStatus = getEffectiveBuildStatus(
- deployment.status,
- deployment.$createdAt,
+ deployment,
$regionalConsoleVariables
)}
{deployment.$id}
- {@const status = deployment.status}
{@const effectiveStatus = getEffectiveBuildStatus(
- status,
- deployment.$createdAt,
+ deployment,
$regionalConsoleVariables
)}
{#if activeDeployment?.$id === deployment?.$id}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
index a942cc3c0..25c021aa6 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/+page.ts
@@ -28,6 +28,7 @@ export const load = async ({ params, depends, url, route, parent }) => {
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
+ Query.orderDesc('$updatedAt'),
...parsedQueries.values()
],
search: search || undefined
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
index f741c03ee..5f36c6bc1 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/store.ts
@@ -15,5 +15,12 @@ export const columns = writable([
title: 'Target',
type: 'string',
width: { min: 120, max: 400 }
+ },
+
+ {
+ id: 'updated',
+ title: '',
+ type: 'string',
+ width: { min: 160, max: 180 }
}
]);
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
index b89528517..a3921aeaa 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/domains/table.svelte
@@ -25,6 +25,7 @@
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
import DnsRecordsAction from '$lib/components/domains/dnsRecordsAction.svelte';
import ViewLogsModal from '$lib/components/domains/viewLogsModal.svelte';
+ import { timeFromNowShort } from '$lib/helpers/date';
let {
proxyRules,
@@ -46,6 +47,28 @@
? 'Deployed from ' + proxy.deploymentVcsProviderBranch
: 'Active deployment';
};
+
+ function updatedLabel(proxyRule: Models.ProxyRule): string {
+ if (proxyRule.status === 'verified') {
+ return '';
+ }
+
+ const timeStr = timeFromNowShort(proxyRule.$updatedAt);
+ if (timeStr === 'n/a') {
+ return '';
+ }
+
+ const prefix =
+ proxyRule.status === 'created'
+ ? 'Checked'
+ : proxyRule.status === 'verifying'
+ ? 'Updated'
+ : proxyRule.status === 'unverified'
+ ? 'Failed'
+ : '';
+
+ return prefix + ' ' + timeStr;
+ }
@@ -117,6 +140,15 @@
{:else if column.id === 'target'}
{proxyTarget(proxyRule)}
+ {:else if column.id === 'updated' && proxyRule.status !== 'verified'}
+
+
+ {updatedLabel(proxyRule)}
+
+
{/if}
{/each}
diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte
index da4890035..de2504715 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte
@@ -1,5 +1,10 @@
diff --git a/src/routes/(console)/project-[region]-[project]/sites/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/table.svelte
index 680c82d38..3f15ba97d 100644
--- a/src/routes/(console)/project-[region]-[project]/sites/table.svelte
+++ b/src/routes/(console)/project-[region]-[project]/sites/table.svelte
@@ -1,7 +1,7 @@