diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/mongo-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/mongo-db.svg new file mode 100644 index 000000000..98239f728 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/dark/mongo-db.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg new file mode 100644 index 000000000..0acf91cae --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/documents-db.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/mongo-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/mongo-db.svg new file mode 100644 index 000000000..5f4f09c80 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/mongo-db.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg b/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg new file mode 100644 index 000000000..59c64a58e --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/(assets)/tables-db.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte index 6044cf62f..a81fd1876 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/+page.svelte @@ -1,35 +1,29 @@ - - {#if $canWriteDatabases} - (showCreate = true)}> - - Create database - - {/if} - - {#if data.databases.total} + {@render containerHeader()} + {#if data.view === 'grid'} - + {:else} {/if} @@ -84,13 +67,33 @@ secondary>Clear Search {:else} - (showCreate = true)} /> + { + await goto( + withPath( + resolveRoute( + '/(console)/project-[region]-[project]/databases/create', + page.params + ), + `?type=${type}` + ) + ); + }} /> {/if} - +{#snippet containerHeader()} + + {#if $canWriteDatabases} + + + Create database + + {/if} + +{/snippet} diff --git a/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte new file mode 100644 index 000000000..76b7fa1e0 --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/create/+page.svelte @@ -0,0 +1,296 @@ + + + + + + + + + + {#if !showCustomId} + + (showCustomId = true)}> + + Database ID + + + {/if} + + + + + + {#if typeFromParams === null} + + {@render selectDatabaseType()} + + {/if} + + + {#if isCloud} + {@render cloudBackupOptions()} + {:else} + {@render selfHostedBackupOptions()} + {/if} + + + + + + (showExitModal = true)}> + Cancel + + formComponent.triggerSubmit()} + >Create + + + + +{#snippet cloudBackupOptions()} + {#if $organization?.billingPlan === BillingPlan.FREE} + + {#each Array.from({ length: 3 }) as _} + + One backup every 24 hours, retained for 30 days + + {/each} + + {:else} + + Upgrade your plan to ensure your data stays safe and backed up. + + Upgrade plan + + + {/if} +{/snippet} + +{#snippet selfHostedBackupOptions()} + + {@const length = $isTabletViewport ? 2 : 3} + {@const gridsColumn = $isSmallViewport ? 2 : 3} + + {#if $isSmallViewport} + + + + + + + {:else if $isTabletViewport} + + + + + + + {:else} + {#each Array.from({ length }) as _} + + One backup every 24 hours, retained for 30 days + + {/each} + {/if} + + + + + + Database Backups are available on Appwrite Cloud + + + + Sign up now to access Appwrite's backups. Schedule automatic or manual backups + to protect your data and ensure quick recovery. + + + + + Sign up + + + +{/snippet} + +{#snippet selectDatabaseType()} + + {#each databaseTypes as databaseType} + + {databaseType.subtitle} + + {/each} + +{/snippet} diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts index 739e07434..d152b8df6 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/init.ts @@ -17,7 +17,7 @@ export type Terminologies = { analytics: AnalyticsResult; terminology: TerminologyResult; dependencies: DependenciesResult; - databaseSdk: DatabaseSdkResult; + databasesSdk: DatabaseSdkResult; }; export function getTerminologies(): Terminologies { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts index 54220872a..5700e1938 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(entity)/helpers/sdk.ts @@ -5,6 +5,14 @@ import { type DatabaseType, type Entity, type EntityList, toSupportiveEntity } f import type { Models } from '@appwrite.io/console'; export type DatabaseSdkResult = { + create: ( + type: DatabaseType, + params: { + databaseId: string; + name: string; + enabled?: boolean; + } + ) => Promise; list: (params: { queries?: string[]; search?: string }) => Promise; getEntity: (params: { databaseId: string; @@ -42,6 +50,22 @@ export function useDatabasesSdk( const baseSdk = sdk.forProject(region, project); return { + async create(type, params): Promise { + switch (type) { + case 'legacy': /* databases api */ + case 'tablesdb': { + return await baseSdk.tablesDB.create(params); + } + case 'documentsdb': { + return await baseSdk.documentsDB.create(params); + } + case 'vectordb': + throw new Error(`Database type not supported yet`); + default: + throw new Error('Unknown database type'); + } + }, + async list(params): Promise { const results = await Promise.all([ baseSdk.tablesDB.list(params) @@ -74,7 +98,7 @@ export function useDatabasesSdk( case 'vectordb': throw new Error(`Database type not supported yet`); default: - throw new Error(`Unknown database type`); + throw new Error('Unknown database type'); } }, diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte index 16666a904..be98a0ee2 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/input.svelte @@ -16,19 +16,39 @@ const featureActive = $derived(isCloud); const { terminology } = getTerminologies(); + + const type = terminology.type; const field = terminology.field.lower; const entity = terminology.entity.lower.singular; const title = $derived.by(() => { - return featureActive - ? `Smart ${field.singular} suggestions` - : `Smart ${field.singular} suggestions available on Cloud`; + switch (type) { + default: + case 'legacy': + case 'tablesdb': + return featureActive + ? `Smart ${field.singular} suggestions` + : `Smart ${field.singular} suggestions available on Cloud`; + + case 'documentsdb': + return featureActive ? `Sample Data` : `Sample Data available on Cloud`; + } }); const subtitle = $derived.by(() => { - return featureActive - ? `Enable AI to suggest useful ${field.plural} based on your ${entity} name` - : `Sign up for Cloud to generate ${field.plural} based on your ${entity} name`; + switch (type) { + default: + case 'legacy': + case 'tablesdb': + return featureActive + ? `Enable AI to suggest useful ${field.plural} based on your ${entity} name` + : `Sign up for Cloud to generate ${field.plural} based on your ${entity} name`; + + case 'documentsdb': + return featureActive + ? `Enable AI to generate sample documents based on your ${entity} name` + : `Sign up for Cloud to generate sample documents based on your ${entity} name`; + } }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts index d469b86b3..2c097408d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+layout.ts @@ -1,8 +1,8 @@ -import { sdk } from '$lib/stores/sdk'; -import { Dependencies } from '$lib/constants'; -import type { LayoutLoad } from './$types'; -import Breadcrumbs from './breadcrumbs.svelte'; import Header from './header.svelte'; +import { sdk } from '$lib/stores/sdk'; +import type { LayoutLoad } from './$types'; +import { Dependencies } from '$lib/constants'; +import Breadcrumbs from './breadcrumbs.svelte'; import SubNavigation from './subNavigation.svelte'; export const load: LayoutLoad = async ({ params, depends }) => { diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte index 90066c9f9..e9bf52da2 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/+page.svelte @@ -34,6 +34,17 @@ function getImageRoute(type: 'light' | 'dark'): string { return withPath(resolveRoute('/'), `/images/empty-database-${type}.svg`); } + + const emptyPageText = $derived.by(() => { + switch (terminology.type) { + default: + case 'legacy': + case 'tablesdb': + return `Create, organize, and query structured data with ${entityTitle.plural}.`; + case 'documentsdb': + return `Create, organize, and query flexible data with ${entityTitle.plural}.`; + } + }); @@ -53,7 +64,7 @@ {#if $canWriteTables} ($showCreateEntity = true)}> - Create table + Create {entityLower.singular} {/if} @@ -86,9 +97,7 @@ - - Create, organize, and query structured data with {entityTitle.plural}. - + {emptyPageText} { + const params = page.params; + const project = page.data.project; + const database = page.data.database; + const organization = page.data.organization as Organization; - const breadcrumbs = $derived([ - { - href: resolveRoute('/(console)/organization-[organization]', { - organization: organization?.$id ?? project.teamId - }), - title: organization.name - }, - { - href: resolveRoute('/(console)/project-[region]-[project]', params), - title: project.name - }, - { - href: resolveRoute('/(console)/project-[region]-[project]/databases', params), - title: 'Databases' - }, - { - href: resolveRoute( - '/(console)/project-[region]-[project]/databases/database-[database]', - params - ), - title: database.name - } - ]); + return [ + { + href: resolveRoute('/(console)/organization-[organization]', { + organization: organization?.$id ?? project.teamId + }), + title: organization.name + }, + { + href: resolveRoute('/(console)/project-[region]-[project]', params), + title: project.name + }, + { + href: resolveRoute('/(console)/project-[region]-[project]/databases', params), + title: 'Databases' + }, + { + href: resolveRoute( + '/(console)/project-[region]-[project]/databases/database-[database]', + params + ), + title: database.name + } + ]; + }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte index 2ab3fa80d..703221534 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/delete.svelte @@ -24,7 +24,7 @@ let entityItems = $state([]); let entities: EntityList | null = $state(null); - const { databaseSdk } = getTerminologies(); + const { databasesSdk } = getTerminologies(); const database = $derived(page.data.database); function buildQueries(): string[] { @@ -48,7 +48,7 @@ try { const queries = buildQueries(); - entities = await databaseSdk.listEntities({ + entities = await databasesSdk.listEntities({ databaseId: page.params.database, queries }); @@ -71,7 +71,7 @@ const handleDelete = async () => { try { - await databaseSdk.delete({ + await databasesSdk.delete({ databaseId: page.params.database }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte index 9a0914345..da68b992d 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/settings/+page.svelte @@ -27,15 +27,15 @@ let errorType: 'error' | 'warning' | 'success' = $state('error'); let showError: false | 'name' | 'email' | 'password' = $state(false); - const { databaseSdk, terminology } = getTerminologies(); + const { databasesSdk, terminology } = getTerminologies(); onMount(async () => { databaseName ??= database.name; }); async function loadEntityCount() { - const { total } = await databaseSdk.listEntities({ - databaseId: database.$id, + const { total } = await databasesSdk.listEntities({ + databaseId: page.params.database, queries: [Query.limit(1)] }); diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editRelated.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editRelated.svelte index 292dc251b..72a473586 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editRelated.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/rows/editRelated.svelte @@ -41,7 +41,7 @@ let workData = $state>>(new Map()); let columnFormWrapper = $state(null); - const { databaseSdk } = getTerminologies(); + const { databasesSdk } = getTerminologies(); onMount(() => { if (rows && tableId) { @@ -89,7 +89,7 @@ }); if (missingTableIds.length > 0) { - const tablesResponse = await databaseSdk.listEntities({ + const tablesResponse = await databasesSdk.listEntities({ databaseId, queries: [ Query.equal('$id', missingTableIds), diff --git a/src/routes/(console)/project-[region]-[project]/databases/empty.svelte b/src/routes/(console)/project-[region]-[project]/databases/empty.svelte new file mode 100644 index 000000000..d0daf3dcc --- /dev/null +++ b/src/routes/(console)/project-[region]-[project]/databases/empty.svelte @@ -0,0 +1,129 @@ + + +{#if $isSmallViewport} + {@render mainContentView()} +{:else} + + {@render mainContentView()} + +{/if} + +{#snippet mainContentView()} + + + Create your first database + + Store, organize, and manage your app data + + + + + + {@render databaseTypeCard({ + type: 'tablesdb', + title: 'TablesDB', + subtitle: + 'Structure your data in rows and columns. Best for relational data and advanced querying.', + image: TablesDB + })} + + + {@render databaseTypeCard({ + type: 'documentsdb', + title: 'DocumentsDB', + subtitle: + 'Store flexible data without a fixed schema. Best for unstructured data and simple querying.', + image: DocumentsDB, + footer: true + })} + +{/snippet} + +{#snippet databaseTypeCard({ type, title, subtitle, image = undefined, footer = false })} + onDatabaseTypeSelected?.(type)}> + + + + + + + {title} + + + + + {subtitle} + + + + {#if footer} + Powered by + + + {/if} + + + + +{/snippet} + + diff --git a/src/routes/(console)/project-[region]-[project]/databases/grid.svelte b/src/routes/(console)/project-[region]-[project]/databases/grid.svelte index cacfeba9c..99e3fc846 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/grid.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/grid.svelte @@ -1,36 +1,67 @@ (showCreate = true)} + disableEmpty={!$canWriteDatabases} + on:click={onCreateDatabaseClick} event="database" service="databases"> {#each data.databases.databases as database} - - {database.name} + + + + {database.name} + + + + + {#if data.lastBackups && data.lastBackups[database.$id]} Last backup: {data.lastBackups[database.$id]} {:else if !data.policies || !data.policies[database.$id]} - No backup policies + + + No backup policies + {:else} Last backup: No backups yet {/if} + {database.$id} {/each} + Create a database diff --git a/src/routes/(console)/project-[region]-[project]/databases/store.ts b/src/routes/(console)/project-[region]-[project]/databases/store.ts index 0abd5c32f..d503891d3 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/store.ts +++ b/src/routes/(console)/project-[region]-[project]/databases/store.ts @@ -1,20 +1,37 @@ -import type { Column } from '$lib/helpers/types'; 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 ? [ { id: '$id', title: 'Database ID', type: 'string', width: 200 }, - { id: 'name', title: 'Name', type: 'string', width: { min: 120 } }, - { id: 'backup', title: 'Backups', type: 'string', width: { min: 120 } }, - { id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } }, - { id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } } + { id: 'type', title: 'Type', type: 'string', width: { min: 180 } }, + { id: 'name', title: 'Name', type: 'string', width: { min: 180 } }, + { id: 'backup', title: 'Backups', type: 'string', width: { min: 180 } }, + { id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 180 } }, + { id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 180 } } ] : [ { id: '$id', title: 'Database ID', type: 'string', width: 200 }, - { id: 'name', title: 'Name', type: 'string', width: { min: 120 } }, - { id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 120 } }, - { id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 120 } } + { id: 'type', title: 'Type', type: 'string', width: { min: 180 } }, + { id: 'name', title: 'Name', type: 'string', width: { min: 180 } }, + { id: '$createdAt', title: 'Created', type: 'datetime', width: { min: 180 } }, + { id: '$updatedAt', title: 'Updated', type: 'datetime', width: { min: 180 } } ] ); + +export function getDatabaseTypeTitle(database: Models.Database) { + switch (database.type as DatabaseType) { + default: + case 'legacy': + case 'tablesdb': + return 'TablesDB'; + case 'documentsdb': + return 'DocumentsDB'; + case 'vectordb': + return 'VectorDB'; + } +} diff --git a/src/routes/(console)/project-[region]-[project]/databases/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/table.svelte index 6054d6f73..6923acf61 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/table.svelte @@ -1,6 +1,6 @@
Create a database