diff --git a/bun.lock b/bun.lock index 7356070504..798c00a88a 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,7 @@ "name": "@appwrite/console", "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6be9e62", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", @@ -130,7 +130,7 @@ "@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="], - "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@6be9e62", { "dependencies": { "json-bigint": "1.0.0" } }], + "@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", { "dependencies": { "json-bigint": "1.0.0" } }], "@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="], diff --git a/package.json b/package.json index 307eefdeb9..fd0ebc590d 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@ai-sdk/svelte": "^1.1.24", - "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@6be9e62", + "@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@ed09983", "@appwrite.io/pink-icons": "0.25.0", "@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3", "@appwrite.io/pink-legacy": "^1.0.3", diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index e7e014de9e..ebd6e8abf0 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -259,6 +259,7 @@ export enum Submit { ProjectService = 'submit_project_service', ProjectUpdateSMTP = 'submit_project_update_smtp', ProjectUpdateOAuth2Server = 'submit_project_update_oauth2_server', + ProjectUsageExecutionsBreakdown = 'submit_project_usage_executions_breakdown', ProjectResume = 'submit_project_resume', MemberCreate = 'submit_member_create', MemberDelete = 'submit_member_delete', @@ -283,6 +284,7 @@ export enum Submit { AuthCorporateEmailsUpdate = 'submit_auth_corporate_emails_update', AuthSessionAlertsUpdate = 'submit_auth_session_alerts_update', AuthMembershipPrivacyUpdate = 'submit_auth_membership_privacy_update', + AuthMfaFactorsUpdate = 'submit_auth_mfa_factors_update', AuthMockNumbersUpdate = 'submit_auth_mock_numbers_update', AuthInvalidateSession = 'submit_auth_invalidate_session', SessionsLengthUpdate = 'submit_sessions_length_update', diff --git a/src/lib/components/csvImportBox.svelte b/src/lib/components/csvImportBox.svelte index 6069ac4c41..35505191b3 100644 --- a/src/lib/components/csvImportBox.svelte +++ b/src/lib/components/csvImportBox.svelte @@ -73,8 +73,8 @@ if (importData.source.toLowerCase() !== 'csv') return; const status = importData.status; - const resourceId = importData.resourceId ?? ''; - const [databaseId, tableId] = resourceId.split(':') ?? []; + const databaseId = importData.parentResourceId ?? ''; + const tableId = importData.resourceId ?? ''; const current = importItems.get(importData.$id); let tableName = current?.table ?? null; diff --git a/src/lib/helpers/oauth2-cimd.ts b/src/lib/helpers/oauth2-cimd.ts index 7d631f678e..e9ec0c288a 100644 --- a/src/lib/helpers/oauth2-cimd.ts +++ b/src/lib/helpers/oauth2-cimd.ts @@ -69,6 +69,7 @@ export function cimdDocumentToApp(clientId: string, document: unknown): Models.A : [], tagline: '', tags: [], + labels: [], images: [], supportUrl: '', dataDeletionUrl: '', @@ -83,6 +84,8 @@ export function cimdDocumentToApp(clientId: string, document: unknown): Models.A deviceFlow: Array.isArray(doc.grant_types) && doc.grant_types.includes(DEVICE_GRANT_TYPE), teamId: '', userId: '', + installationScopes: [], + installationRedirectUrl: '', secrets: [] }; } diff --git a/src/lib/sdk/usage.ts b/src/lib/sdk/usage.ts index b446bfa50f..d84a0cc26f 100644 --- a/src/lib/sdk/usage.ts +++ b/src/lib/sdk/usage.ts @@ -1,4 +1,12 @@ -import type { Models } from '@appwrite.io/console'; +import { + Query, + UsageEventDimension, + UsageEventMetric, + UsageOrderBy, + UsageOrderDirection, + type Models +} from '@appwrite.io/console'; +import { sdk } from '$lib/stores/sdk'; export function accumulateUsage(usage: Models.Metric[], base: number): Models.Metric[] { const accumulation = usage.reduce( @@ -18,6 +26,64 @@ export function accumulateUsage(usage: Models.Metric[], base: number): Models.Me return accumulation.metrics; } +export type ExecutionsBreakdown = { + resourceId: string; + name?: string; + value: number; +}; + +const executionsBreakdownLimit = 25; + +/** + * `UsageProject.executionsBreakdown` was dropped in SDK 16; the per-resource split now comes from + * the dimensional usage API, which returns bare resource IDs, so names are resolved separately. + * + * Scoped to `functions.executions` rather than the umbrella `executions` metric, which also counts + * site executions — those resolve to no name here and their rows link to a function that does not exist. + * Omitting `interval` makes each point a whole-window aggregate per resource, so the limit is a + * top-N-by-total rather than a truncation of the underlying data. + */ +export async function listExecutionsBreakdown( + region: string, + projectId: string, + startAt: string, + endAt: string +): Promise { + const project = sdk.forProject(region, projectId); + + const events = await project.usage.listEvents({ + metrics: [UsageEventMetric.FunctionsExecutions], + dimensions: [UsageEventDimension.ResourceId], + startAt, + endAt, + orderBy: UsageOrderBy.Value, + orderDir: UsageOrderDirection.Desc, + limit: executionsBreakdownLimit + }); + + const totals = new Map(); + for (const metric of events.metrics) { + for (const point of metric.points) { + if (!point.resourceId) continue; + totals.set(point.resourceId, (totals.get(point.resourceId) ?? 0) + point.value); + } + } + + if (totals.size === 0) return []; + + const resourceIds = [...totals.keys()]; + const functions = await project.functions.list({ + queries: [Query.equal('$id', resourceIds), Query.limit(resourceIds.length)] + }); + const names = new Map(functions.functions.map((func) => [func.$id, func.name])); + + return resourceIds.map((resourceId) => ({ + resourceId, + name: names.get(resourceId), + value: totals.get(resourceId) + })); +} + export type Metric = { /** * The value of this metric at the timestamp. diff --git a/src/lib/stores/sdk.ts b/src/lib/stores/sdk.ts index cdea8ebd19..47115c8174 100644 --- a/src/lib/stores/sdk.ts +++ b/src/lib/stores/sdk.ts @@ -8,8 +8,8 @@ import { Backups, Client, Console, + Embeddings, Functions, - Health, Locale, Messaging, Migrations, @@ -31,6 +31,7 @@ import { Webhooks, Realtime, Organizations, + Usage, VectorsDB } from '@appwrite.io/console'; import { buildRegionalV1Endpoint } from '$lib/helpers/apiEndpoint'; @@ -54,7 +55,6 @@ function createConsoleSdk(client: Client) { oauth2: new Oauth2(client), avatars: new Avatars(client), functions: new Functions(client), - health: new Health(client), locale: new Locale(client), projects: new Projects(client), teams: new Teams(client), @@ -115,7 +115,6 @@ const sdkForProject = { avatars: new Avatars(clientProject), backups: new Backups(clientProject), functions: new Functions(clientProject), - health: new Health(clientProject), locale: new Locale(clientProject), messaging: new Messaging(clientProject), project: new Project(clientProject), @@ -131,6 +130,8 @@ const sdkForProject = { tablesDB: new TablesDB(clientProject), documentsDB: new DocumentsDB(clientProject), vectorsDB: new VectorsDB(clientProject), + embeddings: new Embeddings(clientProject), + usage: new Usage(clientProject), webhooks: new Webhooks(clientProject), console: new Console(clientProject) // for suggestions API }; diff --git a/src/routes/(console)/account/payments/addressModal.svelte b/src/routes/(console)/account/payments/addressModal.svelte index af186cb103..57ea22c101 100644 --- a/src/routes/(console)/account/payments/addressModal.svelte +++ b/src/routes/(console)/account/payments/addressModal.svelte @@ -10,7 +10,7 @@ import type { Models } from '@appwrite.io/console'; export let show = false; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let organization: string = null; export let countryList: Models.CountryList; diff --git a/src/routes/(console)/account/payments/billingAddress.svelte b/src/routes/(console)/account/payments/billingAddress.svelte index 703ad7e902..5e9b888376 100644 --- a/src/routes/(console)/account/payments/billingAddress.svelte +++ b/src/routes/(console)/account/payments/billingAddress.svelte @@ -29,7 +29,7 @@ export let data: PageData; - const locale: Models.CloudLocale = data.locale; + const locale: Models.Locale = data.locale; const countryList: Models.CountryList = data.countryList; let show = false; diff --git a/src/routes/(console)/account/payments/editAddressModal.svelte b/src/routes/(console)/account/payments/editAddressModal.svelte index 0fc1fdad9a..891b65103f 100644 --- a/src/routes/(console)/account/payments/editAddressModal.svelte +++ b/src/routes/(console)/account/payments/editAddressModal.svelte @@ -10,7 +10,7 @@ import type { Models } from '@appwrite.io/console'; export let show = false; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let countryList: Models.CountryList; export let selectedAddress: Models.BillingAddress; diff --git a/src/routes/(console)/organization-[organization]/billing/billingAddress.svelte b/src/routes/(console)/organization-[organization]/billing/billingAddress.svelte index 6dbc5bfabb..ebecd52319 100644 --- a/src/routes/(console)/organization-[organization]/billing/billingAddress.svelte +++ b/src/routes/(console)/organization-[organization]/billing/billingAddress.svelte @@ -22,7 +22,7 @@ } from '@appwrite.io/pink-icons-svelte'; import type { Models } from '@appwrite.io/console'; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let countryList: Models.CountryList; export let organization: Models.Organization; export let billingAddress: Models.BillingAddress; diff --git a/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte b/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte index 926f21319e..56798f55b3 100644 --- a/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte +++ b/src/routes/(console)/organization-[organization]/billing/replaceAddress.svelte @@ -13,7 +13,7 @@ import type { Models } from '@appwrite.io/console'; export let show = false; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let countryList: Models.CountryList; let loading = true; diff --git a/src/routes/(console)/organization-[organization]/settings/Soc2.svelte b/src/routes/(console)/organization-[organization]/settings/Soc2.svelte index 7d71dbd609..167a802613 100644 --- a/src/routes/(console)/organization-[organization]/settings/Soc2.svelte +++ b/src/routes/(console)/organization-[organization]/settings/Soc2.svelte @@ -5,7 +5,7 @@ import type { Models } from '@appwrite.io/console'; let show = false; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let countryList: Models.CountryList; diff --git a/src/routes/(console)/organization-[organization]/settings/Soc2Modal.svelte b/src/routes/(console)/organization-[organization]/settings/Soc2Modal.svelte index 34331de126..c4b3b645d1 100644 --- a/src/routes/(console)/organization-[organization]/settings/Soc2Modal.svelte +++ b/src/routes/(console)/organization-[organization]/settings/Soc2Modal.svelte @@ -11,7 +11,7 @@ import type { Models } from '@appwrite.io/console'; export let show = false; - export let locale: Models.CloudLocale; + export let locale: Models.Locale; export let countryList: Models.CountryList; let email = ''; 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 64737bb7f8..47ea7b2505 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/security/+page.svelte @@ -8,6 +8,7 @@ import UpdateSessionsLimit from './updateSessionsLimit.svelte'; import PasswordPolicies from './passwordPolicies.svelte'; import PasswordStrengthPolicy from './passwordStrengthPolicy.svelte'; + import UpdateMfaFactors from './updateMfaFactors.svelte'; import SessionSecurity from './sessionSecurity.svelte'; import UpdateSignupEmailSecurity from './updateSignupEmailSecurity.svelte'; import { isCloud } from '$lib/system'; @@ -25,6 +26,9 @@ dictionaryPolicy={data.passwordDictionaryPolicy} historyPolicy={data.passwordHistoryPolicy} personalDataPolicy={data.passwordPersonalDataPolicy} /> + {#if data.mfaFactorsPolicy} + + {/if} {#if isCloud} { ) as Partial>; return { + mfaFactorsPolicy: policiesById[ProjectPolicyId.Mfafactors] as + | Models.PolicyMfaFactors + | undefined, membershipPrivacyPolicy: policiesById[ ProjectPolicyId.Membershipprivacy ] as Models.PolicyMembershipPrivacy, diff --git a/src/routes/(console)/project-[region]-[project]/auth/security/updateMfaFactors.svelte b/src/routes/(console)/project-[region]-[project]/auth/security/updateMfaFactors.svelte new file mode 100644 index 0000000000..b435aad413 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/auth/security/updateMfaFactors.svelte @@ -0,0 +1,92 @@ + + +
+ + MFA factors + Choose which factors your users can use to complete a multi-factor authentication challenge. Recovery + codes always remain available as a fallback. + + + + + + + + + + +
diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/+page.ts index 642f277e3d..e219c48f93 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.ts @@ -6,7 +6,7 @@ import { timeFromNow } from '$lib/helpers/date'; import type { PageLoad, RouteParams } from './$types'; import { isSelfHosted } from '$lib/system'; import { isCloud } from '$lib/system'; -import { useDatabaseSdk } from '$database/(entity)'; +import { toDatabaseType, useDatabaseSdk } from '$database/(entity)'; export const load: PageLoad = async ({ url, route, depends, params, parent }) => { depends(Dependencies.DATABASES); @@ -62,7 +62,7 @@ async function fetchDatabasesAndBackups( databases.databases.map(async ({ $id, type }) => { const res = await databaseSdk.listEntities({ databaseId: $id, - databaseType: type, + databaseType: toDatabaseType(type), queries: [Query.limit(1), Query.orderDesc('')] }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts index 02fb339a2a..d803b9e0b3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/terminology.ts @@ -1,15 +1,27 @@ import type { Page } from '@sveltejs/kit'; import { capitalize, plural } from '$lib/helpers/string'; -import { AppwriteException, type TablesDBIndexType, type Models } from '@appwrite.io/console'; +import { + AppwriteException, + type DatabaseType as SdkDatabaseType, + type TablesDBIndexType, + type Models +} from '@appwrite.io/console'; import type { Attributes, Collection, Columns, Table } from '$database/store'; import type { Term, TerminologyResult, TerminologyShape } from '$database/(entity)/helpers/types'; type BaseTerminology = typeof baseTerminology; type ImplementedDBTypes = Omit; -/* manual type for the time being because vectorsdb is pending */ -export type DatabaseType = 'legacy' | 'tablesdb' | 'documentsdb' | 'vectorsdb'; +/** + * The SDK also reports the raw dedicated backings (`mysql`, `postgresql`, `mongodb`), which the + * console has no UI for. Extracting from the SDK keeps the supported set from drifting silently: + * a renamed or dropped SDK value removes the member here and breaks every `case` that uses it. + */ +export type DatabaseType = Extract< + `${SdkDatabaseType}`, + 'legacy' | 'tablesdb' | 'documentsdb' | 'vectorsdb' +>; export type CollectionDatabaseType = Extract; export const DEFAULT_VECTOR_DIMENSION = 768; @@ -73,6 +85,22 @@ export const baseTerminology = { } } as const; +function isSupportedDatabaseType(type: string): type is DatabaseType { + return Object.hasOwn(baseTerminology, type); +} + +/** + * Narrows an API reported database type down to the ones this console implements. + * Throws on the rest, matching how every dispatcher in `sdk.ts` rejects an unknown type. + */ +export function toDatabaseType(type: Models.Database['type'] | DatabaseType): DatabaseType { + if (!isSupportedDatabaseType(type)) { + throw new AppwriteException(`Unsupported database type: ${type}`, 500); + } + + return type; +} + const createTerm = (singular: string, pluralForm: string): Term => { return { singular, plural: pluralForm }; }; @@ -152,15 +180,13 @@ export function toSupportiveRecord(raw: Record | Models.Document | Models.Row): * Use `getTerminologies()` instead when in `database-[database]` routes where context is available. */ export function useTerminology(pageOrType: Page | DatabaseType): TerminologyResult { - const type = - typeof pageOrType === 'object' - ? (pageOrType.data?.database?.type as DatabaseType) - : pageOrType; - if (!type) { + const rawType = typeof pageOrType === 'object' ? pageOrType.data?.database?.type : pageOrType; + if (!rawType) { // strict check because this should always be available! throw new AppwriteException('Database type is required', 500); } + const type = toDatabaseType(rawType); const dbTerminologies = terminologyData[type] || {}; const strictSchema = type === 'legacy' || type === 'tablesdb'; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts index 358f8d97ee..130ba58c64 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.ts @@ -2,7 +2,7 @@ import { Query } from '@appwrite.io/console'; import { getLimit, getPage, getSearch, getView, pageToOffset, View } from '$lib/helpers/load'; import type { PageLoad } from './$types'; import { CARD_LIMIT, Dependencies } from '$lib/constants'; -import { type DatabaseType, useDatabaseSdk } from '$database/(entity)'; +import { toDatabaseType, useDatabaseSdk } from '$database/(entity)'; export const load: PageLoad = async ({ params, url, route, depends, parent }) => { const { database } = await parent(); @@ -14,7 +14,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) => const view = getView(url, route, View.Grid); const offset = pageToOffset(page, limit); - const databaseType = database.type as DatabaseType; + const databaseType = toDatabaseType(database.type); const databaseSdk = useDatabaseSdk(params.region, params.project, databaseType); const entities = await databaseSdk.listEntities({ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte index 51d427291b..5269113176 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/(components)/editor/embeddingModal.svelte @@ -27,7 +27,7 @@ try { const response = await sdk .forProject(page.params.region, page.params.project) - .vectorsDB.createTextEmbeddings({ texts: [content.trim()] }); + .embeddings.createTextEmbeddings({ texts: [content.trim()] }); const embedding = response?.embeddings?.[0]?.embedding; if (embedding?.length) { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte index 12ecab9eaf..6db36f36d6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+layout.svelte @@ -49,6 +49,7 @@ import { SideSheet, EditRecordPermissions, + toDatabaseType, useDatabaseSdk, DEFAULT_VECTOR_DIMENSION, type Field, @@ -77,6 +78,7 @@ let editRecordPermissions: EditRecordPermissions; $: collection = data.collection; + $: databaseType = toDatabaseType(data.database.type); $: basePath = resolveRoute( '/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]', page.params @@ -189,11 +191,7 @@ }); async function handleCreateIndex(index: Index) { - const databaseSdk = useDatabaseSdk( - page.params.region, - page.params.project, - data.database.type - ); + const databaseSdk = useDatabaseSdk(page.params.region, page.params.project, databaseType); await databaseSdk.createIndex({ databaseId: page.params.database, @@ -259,8 +257,7 @@ const { rows, ids } = generateFakeRecords($randomDataModalState.value, fields); documentIds = ids; - const dbType = data.database?.type; - const isVectorsDb = dbType === 'vectorsdb'; + const isVectorsDb = databaseType === 'vectorsdb'; const dimension = collection?.dimension ?? DEFAULT_VECTOR_DIMENSION; // For vectorsdb, wrap fields in metadata and add empty embeddings @@ -344,7 +341,7 @@ }}> { const { database } = await parent(); depends(Dependencies.COLLECTION); - const databaseSdk = useDatabaseSdk(params.region, params.project, database.type); + const databaseSdk = useDatabaseSdk( + params.region, + params.project, + toDatabaseType(database.type) + ); const collection = await databaseSdk.getEntity({ databaseId: params.database, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte index 017e1a03a0..ec2e127667 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/+page.svelte @@ -38,7 +38,7 @@ import { invalidate } from '$app/navigation'; import { hash } from '$lib/helpers/string'; import { Dependencies } from '$lib/constants'; - import { EmptySheet, EmptySheetCards } from '$database/(entity)'; + import { EmptySheet, EmptySheetCards, toDatabaseType } from '$database/(entity)'; import { isCollectionsJsonImportInProgress, noSqlDocument, @@ -55,6 +55,8 @@ const filterColumns = writable([]); + const databaseType = $derived(toDatabaseType(data.database.type)); + let isRefreshing = $state(false); let showImportJson = $state(false); let showImportOptions = $state(false); @@ -105,7 +107,8 @@ .migrations.createJSONImport({ bucketId: pendingFile.bucketId, fileId: pendingFile.$id, - resourceId: `${page.params.database}:${page.params.collection}`, + databaseId: page.params.database, + collectionId: page.params.collection, internalFile: pendingLocalFile, onDuplicate: importOnDuplicate }); @@ -328,7 +331,7 @@ {/snippet} {:else} - + {#snippet actions()} { const { collection, database } = await parent(); @@ -20,7 +20,11 @@ export const load: PageLoad = async ({ params, depends, url, route, parent }) => queries.set(parsedQueries); const currentSort = extractSortFromQueries(parsedQueries); - const collectionSdk = getCollectionService(params.region, params.project, database.type); + const collectionSdk = getCollectionService( + params.region, + params.project, + toDatabaseType(database.type) + ); const documentsPage = await loadGridRows( collection, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export/+page.svelte index a4a2b7e9aa..9d19f13b9c 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/export/+page.svelte @@ -56,7 +56,8 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createJSONExport({ - resourceId: `${page.params.database}:${page.params.collection}`, + databaseId: page.params.database, + collectionId: page.params.collection, filename: filename, columns: [], queries: exportWithFilters ? Array.from(localQueries.values()) : [], diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte index ce5f1aaad6..e8f79981f0 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/collection-[collection]/indexes/+page.svelte @@ -6,6 +6,7 @@ Indexes, EmptySheet, EmptySheetCards, + toDatabaseType, useDatabaseSdk } from '$database/(entity)'; import { IconPlus } from '@appwrite.io/pink-icons-svelte'; @@ -15,7 +16,8 @@ let createIndexRef: CreateIndexForm; - const databaseSdk = useDatabaseSdk(page.params.region, page.params.project, data.database.type); + const databaseType = $derived(toDatabaseType(data.database.type)); + const databaseSdk = useDatabaseSdk(page.params.region, page.params.project, databaseType); async function onCreateIndex(index: CreateIndexesCallbackType) { await databaseSdk.createIndex({ @@ -46,14 +48,14 @@ {#snippet createIndexForm()} {/snippet} {#snippet emptyIndexesSheetView(toggle)} - + {#snippet actions()} void } | undefined; diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts index cd5d75e6db..021c3c00a5 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+layout.ts @@ -1,7 +1,7 @@ import Header from './header.svelte'; import type { LayoutLoad } from './$types'; import { Dependencies } from '$lib/constants'; -import { Breadcrumbs, useDatabaseSdk } from '$database/(entity)'; +import { Breadcrumbs, toDatabaseType, useDatabaseSdk } from '$database/(entity)'; import { guardResourceBlock } from '$lib/helpers/project'; export const load: LayoutLoad = async ({ params, depends, parent }) => { @@ -9,7 +9,11 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => { depends(Dependencies.TABLE); guardResourceBlock(project, ['tables', 'collections'], params.table); - const databaseSdk = useDatabaseSdk(params.region, params.project, database.type); + const databaseSdk = useDatabaseSdk( + params.region, + params.project, + toDatabaseType(database.type) + ); const table = await databaseSdk.getEntity({ databaseId: params.database, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte index f1fe008679..89444b447a 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte @@ -139,7 +139,8 @@ .migrations.createCSVImport({ bucketId: pendingFile.bucketId, fileId: pendingFile.$id, - resourceId: `${page.params.database}:${page.params.table}`, + databaseId: page.params.database, + collectionId: page.params.table, internalFile: pendingLocalFile, onDuplicate: importOnDuplicate }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte index 9ed600090f..8291162dbb 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte @@ -101,7 +101,8 @@ await sdk .forProject(page.params.region, page.params.project) .migrations.createCSVExport({ - resourceId: `${page.params.database}:${page.params.table}`, + databaseId: page.params.database, + collectionId: page.params.table, filename: filename, columns: selectedCols, queries: exportWithFilters ? Array.from(localQueries.values()) : [], diff --git a/src/routes/(console)/project-[region]-[project]/databases/store.ts b/src/routes/(console)/project-[region]-[project]/databases/store.ts index 79d0aa31c2..05d7e32127 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/store.ts @@ -2,7 +2,6 @@ import { isCloud } from '$lib/system'; import { writable } from 'svelte/store'; import type { Column } from '$lib/helpers/types'; import type { Models } from '@appwrite.io/console'; -import type { DatabaseType } from '$database/(entity)'; export const columns = writable( isCloud @@ -24,7 +23,7 @@ export const columns = writable( ); export function getDatabaseTypeTitle(database: Models.Database) { - switch (database.type as DatabaseType) { + switch (database.type) { default: case 'legacy': case 'tablesdb': diff --git a/src/routes/(console)/project-[region]-[project]/databases/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/table.svelte index 9d408cba2f..445641a11b 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/table.svelte @@ -3,7 +3,7 @@ import { columns, getDatabaseTypeTitle } from './store'; import { Id } from '$lib/components'; import type { Models } from '@appwrite.io/console'; - import { useTerminology } from '$database/(entity)'; + import { toDatabaseType, useTerminology } from '$database/(entity)'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { resolveRoute, withPath } from '$lib/stores/navigation'; import { IconExclamation } from '@appwrite.io/pink-icons-svelte'; @@ -35,7 +35,7 @@ } function getEntityUrl(database: Models.Database, entityId: string) { - const terminology = useTerminology(database.type); + const terminology = useTerminology(toDatabaseType(database.type)); const entityType = terminology.entity.lower.singular; return withPath( diff --git a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte index 50e94d3ff0..86f229c05b 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.svelte @@ -321,13 +321,13 @@ data: [...executions.map((e) => [e.date, e.value])] } ]} /> - {#if data.usage.executionsBreakdown?.length > 0} + {#if data.executionsBreakdown?.length > 0} Function Usage - {#each data.usage.executionsBreakdown as func} + {#each data.executionsBreakdown as func} diff --git a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.ts b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.ts index 0efb7c6020..3a365a155e 100644 --- a/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.ts +++ b/src/routes/(console)/project-[region]-[project]/settings/usage/[[invoice]]/+page.ts @@ -1,6 +1,7 @@ import { sdk } from '$lib/stores/sdk'; +import { Submit, trackError } from '$lib/actions/analytics'; import type { PageLoad } from './$types'; -import { accumulateUsage } from '$lib/sdk/usage'; +import { accumulateUsage, listExecutionsBreakdown, type ExecutionsBreakdown } from '$lib/sdk/usage'; import { type Models, Query } from '@appwrite.io/console'; export const load: PageLoad = async ({ params, parent }) => { @@ -35,12 +36,19 @@ export const load: PageLoad = async ({ params, parent }) => { } } - const [invoices, usage] = await Promise.all([ + const [invoices, usage, executionsBreakdown] = await Promise.all([ sdk.forConsole.organizations.listInvoices({ organizationId: organization.$id, queries: [Query.orderDesc('from')] }), - sdk.forProject(region, project).project.getUsage({ startDate, endDate }) + sdk.forProject(region, project).project.getUsage({ startDate, endDate }), + listExecutionsBreakdown(region, project, startDate, endDate).catch( + (error): ExecutionsBreakdown[] => { + // supplementary to the executions chart, so never fail the page for it + trackError(error, Submit.ProjectUsageExecutionsBreakdown); + return []; + } + ) ]); if (currentAggregation) { @@ -70,6 +78,7 @@ export const load: PageLoad = async ({ params, parent }) => { usage, invoices, currentInvoice, - currentAggregation + currentAggregation, + executionsBreakdown }; };