mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Show connection properly
This commit is contained in:
@@ -16,10 +16,12 @@ export type DedicatedDatabase = {
|
||||
storage: number;
|
||||
storageClass: string;
|
||||
hostname: string;
|
||||
port: number;
|
||||
status: 'provisioning' | 'ready' | 'failed' | 'deleting' | 'deleted';
|
||||
containerStatus: 'inactive' | 'starting' | 'running' | null;
|
||||
projectId: string;
|
||||
connectionPort: number;
|
||||
connectionUser: string;
|
||||
connectionPassword: string;
|
||||
connectionString: string;
|
||||
status: 'provisioning' | 'ready' | 'inactive' | 'paused' | 'failed' | 'deleted' | 'restoring' | 'scaling';
|
||||
containerStatus: 'inactive' | 'starting' | 'running' | 'active' | null;
|
||||
highAvailability: boolean;
|
||||
haReplicaCount: number;
|
||||
haSyncMode: string | null;
|
||||
|
||||
+34
-23
@@ -187,15 +187,16 @@ export function useDatabaseSdk(
|
||||
async createEntity(params) {
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
case 'tablesdb': {
|
||||
const table = await baseSdk.tablesDB.createTable({
|
||||
...params,
|
||||
tableId: params.entityId
|
||||
});
|
||||
return toSupportiveEntity(table);
|
||||
}
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support entity creation via Appwrite');
|
||||
case 'documentsdb': {
|
||||
const table = await baseSdk.documentsDB.createCollection({
|
||||
...params,
|
||||
@@ -214,12 +215,15 @@ export function useDatabaseSdk(
|
||||
async listEntities(params) {
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
case 'tablesdb': {
|
||||
const { total, tables } = await baseSdk.tablesDB.listTables(params);
|
||||
return { total, entities: tables.map(toSupportiveEntity) };
|
||||
}
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
// External databases don't have entities managed by Appwrite
|
||||
return { total: 0, entities: [] };
|
||||
}
|
||||
case 'documentsdb': {
|
||||
const { total, collections } =
|
||||
await baseSdk.documentsDB.listCollections(params);
|
||||
@@ -235,15 +239,16 @@ export function useDatabaseSdk(
|
||||
async getEntity(params) {
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
case 'tablesdb': {
|
||||
const table = await baseSdk.tablesDB.getTable({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId
|
||||
});
|
||||
return toSupportiveEntity(table);
|
||||
}
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support entity retrieval via Appwrite');
|
||||
case 'documentsdb': {
|
||||
const table = await baseSdk.documentsDB.getCollection({
|
||||
databaseId: params.databaseId,
|
||||
@@ -280,12 +285,13 @@ export function useDatabaseSdk(
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
return await baseSdk.tablesDB.deleteTable({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId
|
||||
});
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support entity deletion via Appwrite');
|
||||
case 'documentsdb':
|
||||
return await baseSdk.documentsDB.deleteCollection({
|
||||
databaseId: params.databaseId,
|
||||
@@ -302,8 +308,6 @@ export function useDatabaseSdk(
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
return await baseSdk.tablesDB.createRow({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId,
|
||||
@@ -311,6 +315,9 @@ export function useDatabaseSdk(
|
||||
data: params.data,
|
||||
permissions: params.permissions
|
||||
});
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support record creation via Appwrite');
|
||||
case 'documentsdb':
|
||||
return await baseSdk.documentsDB.createDocument({
|
||||
databaseId: params.databaseId,
|
||||
@@ -330,8 +337,6 @@ export function useDatabaseSdk(
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
return await baseSdk.tablesDB.updateRow({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId,
|
||||
@@ -339,6 +344,9 @@ export function useDatabaseSdk(
|
||||
data: params.data,
|
||||
permissions: params.permissions
|
||||
});
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support record updates via Appwrite');
|
||||
case 'documentsdb':
|
||||
return await baseSdk.documentsDB.upsertDocument({
|
||||
databaseId: params.databaseId,
|
||||
@@ -358,14 +366,15 @@ export function useDatabaseSdk(
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
return await baseSdk.tablesDB.updateRow({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId,
|
||||
rowId: params.recordId,
|
||||
permissions: params.permissions
|
||||
});
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support permission updates via Appwrite');
|
||||
case 'documentsdb':
|
||||
return await baseSdk.documentsDB.upsertDocument({
|
||||
databaseId: params.databaseId,
|
||||
@@ -383,9 +392,7 @@ export function useDatabaseSdk(
|
||||
async deleteRecord(params) {
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
case 'tablesdb': {
|
||||
const row = await baseSdk.tablesDB.deleteRow({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId,
|
||||
@@ -393,6 +400,9 @@ export function useDatabaseSdk(
|
||||
});
|
||||
return toSupportiveRecord(row);
|
||||
}
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support record deletion via Appwrite');
|
||||
case 'documentsdb': {
|
||||
const document = await baseSdk.documentsDB.deleteDocument({
|
||||
databaseId: params.databaseId,
|
||||
@@ -411,9 +421,7 @@ export function useDatabaseSdk(
|
||||
async deleteRecords(params) {
|
||||
switch (type ?? params.databaseType) {
|
||||
case 'legacy': /* databases api */
|
||||
case 'tablesdb':
|
||||
case 'prisma':
|
||||
case 'dedicated': {
|
||||
case 'tablesdb': {
|
||||
const { total, rows } = await baseSdk.tablesDB.deleteRows({
|
||||
databaseId: params.databaseId,
|
||||
tableId: params.entityId,
|
||||
@@ -421,6 +429,9 @@ export function useDatabaseSdk(
|
||||
});
|
||||
return { total, records: rows.map(toSupportiveRecord) };
|
||||
}
|
||||
case 'prisma':
|
||||
case 'dedicated':
|
||||
throw new Error('External databases do not support bulk record deletion via Appwrite');
|
||||
case 'documentsdb': {
|
||||
const { total, documents } = await baseSdk.documentsDB.deleteDocuments({
|
||||
databaseId: params.databaseId,
|
||||
|
||||
+1
-12
@@ -4,7 +4,7 @@ import type { LayoutLoad } from './$types';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import Breadcrumbs from './breadcrumbs.svelte';
|
||||
import SubNavigation from './subNavigation.svelte';
|
||||
import type { DedicatedDatabase, DedicatedDatabaseCredentials } from '$lib/sdk/dedicatedDatabases';
|
||||
import type { DedicatedDatabase } from '$lib/sdk/dedicatedDatabases';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
type DatabaseWithType = Models.Database & {
|
||||
@@ -23,7 +23,6 @@ export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
// Try to get from tablesDB first (handles legacy, tablesdb, documentsdb)
|
||||
let database: DatabaseWithType | DedicatedDatabase;
|
||||
let dedicatedDatabase: DedicatedDatabase | null = null;
|
||||
let credentials: DedicatedDatabaseCredentials | null = null;
|
||||
|
||||
try {
|
||||
database = await projectSdk.tablesDB.get({
|
||||
@@ -45,19 +44,9 @@ export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch credentials for dedicated databases
|
||||
if (dedicatedDatabase) {
|
||||
try {
|
||||
credentials = await projectSdk.dedicatedDatabases.getCredentials(params.database);
|
||||
} catch {
|
||||
// Credentials not available yet (e.g., still provisioning)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
database,
|
||||
dedicatedDatabase,
|
||||
credentials,
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
subNavigation: SubNavigation
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
</script>
|
||||
|
||||
{#if data.isDedicatedType && data.dedicatedDatabase}
|
||||
<DedicatedOverview database={data.dedicatedDatabase} credentials={data.credentials} />
|
||||
<DedicatedOverview database={data.dedicatedDatabase} />
|
||||
{:else}
|
||||
<Container databasesMainScreen>
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
|
||||
+3
-5
@@ -5,7 +5,7 @@ import { CARD_LIMIT, Dependencies } from '$lib/constants';
|
||||
import { type DatabaseType, useDatabaseSdk } from '$database/(entity)';
|
||||
|
||||
export const load: PageLoad = async ({ params, url, route, depends, parent }) => {
|
||||
const { database, dedicatedDatabase, credentials } = await parent();
|
||||
const { database, dedicatedDatabase } = await parent();
|
||||
depends(Dependencies.TABLES);
|
||||
|
||||
const databaseType = database.type as DatabaseType;
|
||||
@@ -21,8 +21,7 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
|
||||
view: View.Grid,
|
||||
entities: { total: 0, entities: [] },
|
||||
isDedicatedType: true,
|
||||
dedicatedDatabase,
|
||||
credentials
|
||||
dedicatedDatabase
|
||||
};
|
||||
}
|
||||
|
||||
@@ -46,7 +45,6 @@ export const load: PageLoad = async ({ params, url, route, depends, parent }) =>
|
||||
view,
|
||||
entities,
|
||||
isDedicatedType: false,
|
||||
dedicatedDatabase: null,
|
||||
credentials: null
|
||||
dedicatedDatabase: null
|
||||
};
|
||||
};
|
||||
|
||||
+9
-16
@@ -5,20 +5,15 @@
|
||||
import { IconDuplicate } from '@appwrite.io/pink-icons-svelte';
|
||||
import { copy } from '$lib/helpers/copy';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import type {
|
||||
DedicatedDatabase,
|
||||
DedicatedDatabaseCredentials
|
||||
} from '$lib/sdk/dedicatedDatabases';
|
||||
import type { DedicatedDatabase } from '$lib/sdk/dedicatedDatabases';
|
||||
|
||||
let {
|
||||
show = $bindable(false),
|
||||
database,
|
||||
credentials,
|
||||
connectionCommand
|
||||
}: {
|
||||
show: boolean;
|
||||
database: DedicatedDatabase;
|
||||
credentials: DedicatedDatabaseCredentials | null;
|
||||
connectionCommand: string;
|
||||
} = $props();
|
||||
|
||||
@@ -48,8 +43,8 @@
|
||||
}
|
||||
|
||||
async function copyConnectionString() {
|
||||
if (!credentials) return;
|
||||
const success = await copy(credentials.connectionString);
|
||||
if (!database.connectionString) return;
|
||||
const success = await copy(database.connectionString);
|
||||
if (success) {
|
||||
addNotification({
|
||||
type: 'success',
|
||||
@@ -91,8 +86,8 @@
|
||||
<Typography.Text variant="m-400">
|
||||
Use this URI to connect from your application or database client.
|
||||
</Typography.Text>
|
||||
{#if credentials}
|
||||
<CopyInput value={credentials.connectionString} />
|
||||
{#if database.connectionString}
|
||||
<CopyInput value={database.connectionString} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -116,21 +111,19 @@
|
||||
<div class="reference-grid">
|
||||
<div class="reference-item">
|
||||
<Typography.Text variant="m-500">Host</Typography.Text>
|
||||
<Typography.Text variant="m-400">{credentials?.host ?? '-'}</Typography.Text>
|
||||
<Typography.Text variant="m-400">{database.hostname || '-'}</Typography.Text>
|
||||
</div>
|
||||
<div class="reference-item">
|
||||
<Typography.Text variant="m-500">Port</Typography.Text>
|
||||
<Typography.Text variant="m-400">{credentials?.port ?? '-'}</Typography.Text>
|
||||
<Typography.Text variant="m-400">{database.connectionPort || '-'}</Typography.Text>
|
||||
</div>
|
||||
<div class="reference-item">
|
||||
<Typography.Text variant="m-500">Database</Typography.Text>
|
||||
<Typography.Text variant="m-400"
|
||||
>{credentials?.database ?? '-'}</Typography.Text>
|
||||
<Typography.Text variant="m-400">postgres</Typography.Text>
|
||||
</div>
|
||||
<div class="reference-item">
|
||||
<Typography.Text variant="m-500">Username</Typography.Text>
|
||||
<Typography.Text variant="m-400"
|
||||
>{credentials?.username ?? '-'}</Typography.Text>
|
||||
<Typography.Text variant="m-400">{database.connectionUser || '-'}</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
|
||||
+279
-205
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { CardGrid, CopyInput, Copy } from '$lib/components';
|
||||
import { CardGrid, CopyInput, Copy, Code, Status } from '$lib/components';
|
||||
import { Button, InputSecret } from '$lib/elements/forms';
|
||||
import { Container } from '$lib/layout';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
@@ -9,10 +9,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import type {
|
||||
DedicatedDatabase,
|
||||
DedicatedDatabaseCredentials
|
||||
} from '$lib/sdk/dedicatedDatabases';
|
||||
import type { DedicatedDatabase } from '$lib/sdk/dedicatedDatabases';
|
||||
import {
|
||||
Badge,
|
||||
Layout,
|
||||
@@ -20,49 +17,56 @@
|
||||
Alert,
|
||||
Icon,
|
||||
Input,
|
||||
Skeleton
|
||||
Skeleton,
|
||||
Tabs,
|
||||
Divider,
|
||||
Card
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { IconDuplicate, IconTerminal, IconRefresh } from '@appwrite.io/pink-icons-svelte';
|
||||
import ConnectModal from './connectModal.svelte';
|
||||
import { IconDuplicate, IconRefresh } from '@appwrite.io/pink-icons-svelte';
|
||||
|
||||
const {
|
||||
database,
|
||||
credentials
|
||||
database
|
||||
}: {
|
||||
database: DedicatedDatabase;
|
||||
credentials: DedicatedDatabaseCredentials | null;
|
||||
} = $props();
|
||||
|
||||
let showConnectModal = $state(false);
|
||||
let isRefreshing = $state(false);
|
||||
let isColdStarting = $state(false);
|
||||
let connectionTab = $state<'direct' | 'string'>('direct');
|
||||
|
||||
// Status badge type mapping
|
||||
const statusBadgeType = $derived.by((): 'success' | 'warning' | 'error' | undefined => {
|
||||
// Check if this is a Prisma database
|
||||
const isPrisma = $derived(database.backend === 'prisma');
|
||||
|
||||
// Map database status to Status component status
|
||||
const statusComponentStatus = $derived.by((): 'ready' | 'processing' | 'failed' | 'pending' => {
|
||||
switch (database.status) {
|
||||
case 'ready':
|
||||
return 'success';
|
||||
return 'ready';
|
||||
case 'provisioning':
|
||||
case 'deleting':
|
||||
return 'warning';
|
||||
case 'scaling':
|
||||
case 'restoring':
|
||||
return 'processing';
|
||||
case 'failed':
|
||||
case 'deleted':
|
||||
return 'error';
|
||||
return 'failed';
|
||||
case 'inactive':
|
||||
case 'paused':
|
||||
default:
|
||||
return undefined;
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
// Container status badge type
|
||||
const containerBadgeType = $derived.by((): 'success' | 'warning' | undefined => {
|
||||
// Map container status to Status component status
|
||||
const containerComponentStatus = $derived.by((): 'ready' | 'processing' | 'pending' => {
|
||||
switch (database.containerStatus) {
|
||||
case 'running':
|
||||
return 'success';
|
||||
case 'active':
|
||||
return 'ready';
|
||||
case 'starting':
|
||||
return 'warning';
|
||||
return 'processing';
|
||||
case 'inactive':
|
||||
default:
|
||||
return undefined;
|
||||
return 'pending';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,6 +102,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
function capitalizeFirst(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
isRefreshing = true;
|
||||
try {
|
||||
@@ -142,50 +150,47 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Check if connection details are available
|
||||
const hasConnectionDetails = $derived(
|
||||
database.hostname && database.connectionUser && database.connectionPassword
|
||||
);
|
||||
|
||||
// Generate connection command based on engine
|
||||
function getConnectionCommand(): string {
|
||||
if (!credentials) return '';
|
||||
if (!hasConnectionDetails) return '';
|
||||
|
||||
switch (database.engine) {
|
||||
case 'postgres':
|
||||
return `psql "postgresql://${credentials.username}:${credentials.password}@${credentials.host}:${credentials.port}/${credentials.database}"`;
|
||||
return `psql "${database.connectionString}"`;
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
return `mysql -h ${credentials.host} -P ${credentials.port} -u ${credentials.username} -p${credentials.password} ${credentials.database}`;
|
||||
return `mysql -h ${database.hostname} -P ${database.connectionPort} -u ${database.connectionUser} -p${database.connectionPassword} postgres`;
|
||||
default:
|
||||
return credentials.connectionString;
|
||||
return database.connectionString;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container databasesMainScreen>
|
||||
<!-- Status Section -->
|
||||
<Container>
|
||||
<!-- Status Card -->
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Status</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Stack direction="row" gap="l" alignItems="center">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Database Status</Typography.Text>
|
||||
<Badge
|
||||
type={statusBadgeType}
|
||||
variant="secondary"
|
||||
size="s"
|
||||
content={database.status.charAt(0).toUpperCase() +
|
||||
database.status.slice(1)} />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" gap="xl" alignItems="center" wrap>
|
||||
<Status status={statusComponentStatus}>
|
||||
{capitalizeFirst(database.status)}
|
||||
</Status>
|
||||
|
||||
{#if database.containerStatus}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Container</Typography.Text>
|
||||
<Badge
|
||||
type={containerBadgeType}
|
||||
variant="secondary"
|
||||
size="s"
|
||||
content={database.containerStatus.charAt(0).toUpperCase() +
|
||||
database.containerStatus.slice(1)} />
|
||||
</Layout.Stack>
|
||||
{#if database.containerStatus && !isPrisma}
|
||||
<Status status={containerComponentStatus}>
|
||||
Container: {capitalizeFirst(database.containerStatus)}
|
||||
</Status>
|
||||
{/if}
|
||||
|
||||
<Typography.Text variant="m-500">
|
||||
{database.region.toUpperCase()}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
{#if database.error}
|
||||
@@ -194,14 +199,18 @@
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
|
||||
<div class="grid-1-2-col-2">
|
||||
<p>Created: {toLocaleDateTime(database.$createdAt)}</p>
|
||||
<p>Last updated: {toLocaleDateTime(database.$updatedAt)}</p>
|
||||
</div>
|
||||
<Layout.Stack direction="row" gap="xl" wrap>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Created {toLocaleDateTime(database.$createdAt)}
|
||||
</Typography.Caption>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Updated {toLocaleDateTime(database.$updatedAt)}
|
||||
</Typography.Caption>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if database.containerStatus === 'inactive'}
|
||||
{#if database.containerStatus === 'inactive' && !isPrisma}
|
||||
<Button secondary disabled={isColdStarting} on:click={triggerColdStart}>
|
||||
{isColdStarting ? 'Starting...' : 'Start Database'}
|
||||
</Button>
|
||||
@@ -213,43 +222,72 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<!-- Connection Settings Section -->
|
||||
{#if database.status === 'ready' && credentials}
|
||||
<!-- Connection Settings -->
|
||||
{#if database.status === 'ready' && hasConnectionDetails}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Connection Settings</svelte:fragment>
|
||||
Use these credentials to connect to your database directly.
|
||||
<svelte:fragment slot="title">Connection</svelte:fragment>
|
||||
Use these credentials to connect to your database.
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<CopyInput label="Host" value={credentials.host} />
|
||||
<CopyInput label="Port" value={String(credentials.port)} />
|
||||
<CopyInput label="Database" value={credentials.database} />
|
||||
<CopyInput label="Username" value={credentials.username} />
|
||||
|
||||
<!-- Password with show/hide toggle -->
|
||||
<div class="password-field">
|
||||
<InputSecret id="password" label="Password" value={credentials.password} />
|
||||
<div class="password-copy">
|
||||
<Copy value={credentials.password}>
|
||||
<Input.Action icon={IconDuplicate} />
|
||||
</Copy>
|
||||
</div>
|
||||
<Layout.Stack gap="l">
|
||||
<div>
|
||||
<Tabs.Root variant="secondary" let:root>
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (connectionTab = 'direct')}
|
||||
active={connectionTab === 'direct'}>
|
||||
Direct Connection
|
||||
</Tabs.Item.Button>
|
||||
<Tabs.Item.Button
|
||||
{root}
|
||||
on:click={() => (connectionTab = 'string')}
|
||||
active={connectionTab === 'string'}>
|
||||
Connection String
|
||||
</Tabs.Item.Button>
|
||||
</Tabs.Root>
|
||||
<Divider />
|
||||
</div>
|
||||
|
||||
<CopyInput label="Connection String" value={credentials.connectionString} />
|
||||
{#if connectionTab === 'direct'}
|
||||
<Layout.Grid columns={2} columnsS={1} gap="m">
|
||||
<CopyInput label="Host" value={database.hostname} />
|
||||
<CopyInput label="Port" value={String(database.connectionPort)} />
|
||||
<CopyInput label="Username" value={database.connectionUser} />
|
||||
<div class="password-field">
|
||||
<InputSecret
|
||||
id="password"
|
||||
label="Password"
|
||||
value={database.connectionPassword} />
|
||||
<div class="password-copy">
|
||||
<Copy value={database.connectionPassword}>
|
||||
<Input.Action icon={IconDuplicate} />
|
||||
</Copy>
|
||||
</div>
|
||||
</div>
|
||||
</Layout.Grid>
|
||||
<CopyInput label="Database" value="postgres" />
|
||||
{:else}
|
||||
<Layout.Stack gap="m">
|
||||
<CopyInput
|
||||
label="Connection String"
|
||||
value={database.connectionString} />
|
||||
<Layout.Stack gap="xs">
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
Terminal Command
|
||||
</Typography.Caption>
|
||||
<Code language="bash" code={getConnectionCommand()} withCopy />
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button secondary on:click={() => (showConnectModal = true)}>
|
||||
<Icon icon={IconTerminal} size="s" slot="start" />
|
||||
Connect
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{:else if database.status === 'provisioning'}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Connection Settings</svelte:fragment>
|
||||
<svelte:fragment slot="title">Connection</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Stack gap="l">
|
||||
<Alert.Inline status="info" title="Provisioning in progress">
|
||||
Your database is being set up. Connection details will be available once
|
||||
provisioning is complete.
|
||||
@@ -264,53 +302,69 @@
|
||||
</CardGrid>
|
||||
{/if}
|
||||
|
||||
<!-- Resources Section -->
|
||||
<!-- Resources -->
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Resources</svelte:fragment>
|
||||
Your database configuration and allocated resources.
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Grid columns={2} columnsXS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Engine</Typography.Text>
|
||||
<Typography.Text
|
||||
>{getEngineDisplayName(database.engine)}
|
||||
{database.version}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Tier</Typography.Text>
|
||||
<Typography.Text
|
||||
>{database.tier.charAt(0).toUpperCase() +
|
||||
database.tier.slice(1)}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">CPU</Typography.Text>
|
||||
<Typography.Text>{cpuDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Memory</Typography.Text>
|
||||
<Typography.Text>{memoryDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Storage</Typography.Text>
|
||||
<Typography.Text>{storageDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Storage Class</Typography.Text>
|
||||
<Typography.Text>{database.storageClass}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Grid>
|
||||
</Layout.Stack>
|
||||
<Layout.Grid columns={3} columnsM={2} columnsS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Engine
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{getEngineDisplayName(database.engine)} {database.version}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Tier
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{capitalizeFirst(database.tier)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Backend
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{capitalizeFirst(database.backend)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
CPU
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">{cpuDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Memory
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">{memoryDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Storage
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">{storageDisplay}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Grid>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<!-- High Availability Section -->
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">High Availability</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Grid columns={2} columnsXS={1} gap="l">
|
||||
<!-- High Availability - Only show for non-Prisma databases -->
|
||||
{#if !isPrisma}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">High Availability</svelte:fragment>
|
||||
Configure replicas and failover settings for your database.
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Grid columns={3} columnsM={2} columnsS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Status</Typography.Text>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Status
|
||||
</Typography.Caption>
|
||||
<Badge
|
||||
type={database.highAvailability ? 'success' : undefined}
|
||||
variant="secondary"
|
||||
@@ -319,102 +373,133 @@
|
||||
</Layout.Stack>
|
||||
{#if database.highAvailability}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Replica Count</Typography.Text>
|
||||
<Typography.Text>{database.haReplicaCount}</Typography.Text>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Replicas
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{database.haReplicaCount}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{#if database.haSyncMode}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Sync Mode</Typography.Text>
|
||||
<Typography.Text>{database.haSyncMode}</Typography.Text>
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
Sync Mode
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{capitalizeFirst(database.haSyncMode)}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{/if}
|
||||
</Layout.Grid>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{/if}
|
||||
|
||||
<!-- Network Settings Section -->
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Network</svelte:fragment>
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Grid columns={2} columnsXS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Max Connections</Typography.Text>
|
||||
<Typography.Text>{database.networkMaxConnections}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Idle Timeout</Typography.Text>
|
||||
<Typography.Text>{database.networkIdleTimeoutSeconds}s</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{#if database.idleTimeoutMinutes}
|
||||
<!-- Network - Only show for non-Prisma databases -->
|
||||
{#if !isPrisma}
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Network</svelte:fragment>
|
||||
Connection limits and network configuration.
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="l">
|
||||
<Layout.Grid columns={3} columnsM={2} columnsS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Sleep After Idle</Typography.Text>
|
||||
<Typography.Text>{database.idleTimeoutMinutes} min</Typography.Text>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Max Connections
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{database.networkMaxConnections}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Idle Timeout
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{database.networkIdleTimeoutSeconds}s
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{#if database.idleTimeoutMinutes}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption
|
||||
variant="400"
|
||||
color="--fgcolor-neutral-tertiary">
|
||||
Sleep After Idle
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{database.idleTimeoutMinutes} min
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Grid>
|
||||
|
||||
{#if database.networkIPAllowlist?.length > 0}
|
||||
<Layout.Stack gap="xs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
IP Allowlist
|
||||
</Typography.Caption>
|
||||
<Layout.Stack direction="row" gap="xs" wrap>
|
||||
{#each database.networkIPAllowlist as ip}
|
||||
<Badge variant="secondary" size="s" content={ip} />
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Grid>
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
{/if}
|
||||
|
||||
{#if database.networkIPAllowlist?.length > 0}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">IP Allowlist</Typography.Text>
|
||||
<Layout.Stack gap="xs">
|
||||
{#each database.networkIPAllowlist as ip}
|
||||
<Typography.Text variant="m-400">{ip}</Typography.Text>
|
||||
{/each}
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<!-- Backup Settings Section -->
|
||||
<!-- Backups -->
|
||||
<CardGrid>
|
||||
<svelte:fragment slot="title">Backups</svelte:fragment>
|
||||
Automatic backup and point-in-time recovery settings.
|
||||
<svelte:fragment slot="aside">
|
||||
<Layout.Stack gap="m">
|
||||
<Layout.Grid columns={2} columnsXS={1} gap="l">
|
||||
<Layout.Grid columns={2} columnsS={1} gap="l">
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Automatic Backups
|
||||
</Typography.Caption>
|
||||
<Badge
|
||||
type={database.backupEnabled ? 'success' : undefined}
|
||||
variant="secondary"
|
||||
size="s"
|
||||
content={database.backupEnabled ? 'Enabled' : 'Disabled'} />
|
||||
</Layout.Stack>
|
||||
{#if database.backupEnabled}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Status</Typography.Text>
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Point-in-Time Recovery
|
||||
</Typography.Caption>
|
||||
<Badge
|
||||
type={database.backupEnabled ? 'success' : undefined}
|
||||
type={database.backupPitr ? 'success' : undefined}
|
||||
variant="secondary"
|
||||
size="s"
|
||||
content={database.backupEnabled ? 'Enabled' : 'Disabled'} />
|
||||
content={database.backupPitr ? 'Enabled' : 'Disabled'} />
|
||||
</Layout.Stack>
|
||||
{#if database.backupEnabled}
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500"
|
||||
>Point-in-Time Recovery</Typography.Text>
|
||||
<Badge
|
||||
type={database.backupPitr ? 'success' : undefined}
|
||||
variant="secondary"
|
||||
size="s"
|
||||
content={database.backupPitr ? 'Enabled' : 'Disabled'} />
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Schedule</Typography.Text>
|
||||
<Typography.Text>{database.backupCron}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Text variant="m-500">Retention</Typography.Text>
|
||||
<Typography.Text>{database.backupRetentionDays} days</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Grid>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Schedule
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">{database.backupCron}</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Layout.Stack gap="xxs">
|
||||
<Typography.Caption variant="400" color="--fgcolor-neutral-tertiary">
|
||||
Retention
|
||||
</Typography.Caption>
|
||||
<Typography.Text variant="m-500">
|
||||
{database.backupRetentionDays} days
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Layout.Grid>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Container>
|
||||
|
||||
<ConnectModal
|
||||
bind:show={showConnectModal}
|
||||
{database}
|
||||
{credentials}
|
||||
connectionCommand={getConnectionCommand()} />
|
||||
|
||||
<style lang="scss">
|
||||
.password-field {
|
||||
position: relative;
|
||||
@@ -427,15 +512,4 @@
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.grid-1-2-col-2 {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 0.5rem;
|
||||
color: var(--fgcolor-neutral-secondary);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -23,8 +23,15 @@ export const columns = writable<Column[]>(
|
||||
]
|
||||
);
|
||||
|
||||
export function getDatabaseTypeTitle(database: Models.Database) {
|
||||
export function getDatabaseTypeTitle(database: Models.Database & { engine?: string }) {
|
||||
switch (database.type as DatabaseType) {
|
||||
case 'prisma':
|
||||
return 'Prisma Postgres';
|
||||
case 'dedicated': {
|
||||
const engine = database.engine || 'postgres';
|
||||
const engineName = engine === 'postgres' ? 'PostgreSQL' : engine === 'mysql' ? 'MySQL' : engine;
|
||||
return `Dedicated ${engineName}`;
|
||||
}
|
||||
default:
|
||||
case 'legacy':
|
||||
case 'tablesdb':
|
||||
|
||||
Reference in New Issue
Block a user