mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
refactor: migration export
This commit is contained in:
@@ -17,7 +17,8 @@ export type CommandGroup =
|
||||
| 'storage'
|
||||
| 'domains'
|
||||
| 'webhooks'
|
||||
| 'integrations';
|
||||
| 'integrations'
|
||||
| 'migrations';
|
||||
|
||||
type BaseCommand = {
|
||||
callback: () => void;
|
||||
@@ -264,6 +265,7 @@ export const commandGroupRanks = derived(groupRankTransformations, ($groupRankTr
|
||||
functions: 0,
|
||||
storage: 0,
|
||||
integrations: 0,
|
||||
migrations: 0,
|
||||
help: -1
|
||||
};
|
||||
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
});
|
||||
|
||||
const examples = [
|
||||
'How can I integrate Appwrite with my frontend application?',
|
||||
'How to add platform in the console?',
|
||||
'How can I manage users, permissions, and access control in Appwrite?',
|
||||
'How can I set up database collections and documents in Appwrite?',
|
||||
'How do I configure and manage server-side functions in Appwrite?',
|
||||
'How can I access the audit logs in the console?',
|
||||
'How to add custom domain in the console?'
|
||||
];
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { excludeArray } from '$lib/helpers/array';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export type Provider = 'appwrite' | 'nhost' | 'supabase' | 'firebase';
|
||||
|
||||
const initialFormData = {
|
||||
users: {
|
||||
root: false,
|
||||
@@ -103,3 +100,87 @@ export const migrationFormToResources = (formData: MigrationFormData): Resource[
|
||||
|
||||
return resources;
|
||||
};
|
||||
|
||||
import {
|
||||
PUBLIC_NHOST_TEST_DATABASE,
|
||||
PUBLIC_NHOST_TEST_PASSWORD,
|
||||
PUBLIC_NHOST_TEST_REGION,
|
||||
PUBLIC_NHOST_TEST_SECRET,
|
||||
PUBLIC_NHOST_TEST_SUBDOMAIN,
|
||||
PUBLIC_NHOST_TEST_USERNAME
|
||||
} from '$env/static/public';
|
||||
|
||||
type AppwriteInput = {
|
||||
provider: 'appwrite';
|
||||
endpoint?: string;
|
||||
projectID?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
type FirebaseInput = {
|
||||
provider: 'firebase';
|
||||
serviceAccount?: string;
|
||||
};
|
||||
|
||||
type SupabaseInput = {
|
||||
provider: 'supabase';
|
||||
host?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
endpoint?: string;
|
||||
apiKey?: string;
|
||||
port?: number;
|
||||
};
|
||||
|
||||
type NhostInput = {
|
||||
provider: 'nhost';
|
||||
region?: string;
|
||||
subdomain?: string;
|
||||
database?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
adminSecret?: string;
|
||||
};
|
||||
|
||||
export type ProviderInput = AppwriteInput | NhostInput | SupabaseInput | FirebaseInput;
|
||||
export type Provider = ProviderInput['provider'];
|
||||
|
||||
// const mockProvider: ProviderInput = {
|
||||
// provider: 'appwrite',
|
||||
// endpoint: PUBLIC_MOCK_ENDPOINT,
|
||||
// apiKey: PUBLIC_MOCK_APIKEY,
|
||||
// projectID: PUBLIC_MOCK_PROJECTID
|
||||
// };
|
||||
// const mockProvider: ProviderInput = {
|
||||
// provider: 'supabase',
|
||||
// endpoint: PUBLIC_SUPABASE_TEST_ENDPOINT,
|
||||
// apiKey: PUBLIC_SUPABASE_TEST_KEY,
|
||||
// host: PUBLIC_SUPABASE_TEST_HOST,
|
||||
// port: Number(PUBLIC_SUPABASE_TEST_PORT),
|
||||
// username: PUBLIC_SUPABASE_TEST_USERNAME,
|
||||
// password: PUBLIC_SUPABASE_TEST_PASSWORD
|
||||
// };
|
||||
const mockProvider: ProviderInput = {
|
||||
provider: 'nhost',
|
||||
subdomain: PUBLIC_NHOST_TEST_SUBDOMAIN,
|
||||
region: PUBLIC_NHOST_TEST_REGION,
|
||||
adminSecret: PUBLIC_NHOST_TEST_SECRET,
|
||||
database: PUBLIC_NHOST_TEST_DATABASE,
|
||||
username: PUBLIC_NHOST_TEST_USERNAME,
|
||||
password: PUBLIC_NHOST_TEST_PASSWORD
|
||||
};
|
||||
|
||||
const initialProvider: ProviderInput = { provider: 'appwrite' };
|
||||
export const createMigrationProviderStore = () => {
|
||||
const store = writable<ProviderInput>({ ...mockProvider });
|
||||
|
||||
const changeProvider = (provider: Provider) => {
|
||||
const newProvider: ProviderInput = { provider };
|
||||
store.set(newProvider);
|
||||
};
|
||||
|
||||
return {
|
||||
...store,
|
||||
changeProvider
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
const randRange = (min: number, max: number) => Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
|
||||
type Status = 'idle' | 'running' | 'error' | 'success';
|
||||
|
||||
type State = {
|
||||
items: {
|
||||
databases?: Status;
|
||||
documents?: Status;
|
||||
users?: Status;
|
||||
storage?: Status;
|
||||
};
|
||||
status: Status;
|
||||
};
|
||||
|
||||
function createMigrator() {
|
||||
const { subscribe, update, set } = writable<State>({
|
||||
items: { databases: 'idle', documents: 'running', users: 'error', storage: 'success' },
|
||||
status: 'running'
|
||||
});
|
||||
|
||||
const startMigration = async () => {
|
||||
set({
|
||||
items: {
|
||||
databases: 'idle',
|
||||
documents: 'idle',
|
||||
users: 'idle',
|
||||
storage: 'idle'
|
||||
},
|
||||
status: 'running'
|
||||
});
|
||||
|
||||
const keys = ['databases', 'documents', 'users', 'storage'];
|
||||
|
||||
for (const key of keys) {
|
||||
sleep(randRange(1000, 5000)).then(() => {
|
||||
update((state) => ({
|
||||
...state,
|
||||
items: { ...state.items, [key]: 'running' }
|
||||
}));
|
||||
|
||||
sleep(randRange(1000, 5000)).then(() => {
|
||||
update((state) => ({
|
||||
...state,
|
||||
items: { ...state.items, [key]: 'success' }
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
startMigration
|
||||
};
|
||||
}
|
||||
|
||||
export const migrator = createMigrator();
|
||||
@@ -10,13 +10,12 @@
|
||||
import { user } from '$lib/stores/user';
|
||||
import { ENV, isCloud } from '$lib/system';
|
||||
import * as Sentry from '@sentry/svelte';
|
||||
import LogRocket from 'logrocket';
|
||||
import { BrowserTracing } from '@sentry/tracing';
|
||||
import LogRocket from 'logrocket';
|
||||
import { onMount } from 'svelte';
|
||||
import { onCLS, onFCP, onFID, onINP, onLCP, onTTFB } from 'web-vitals';
|
||||
import Loading from './loading.svelte';
|
||||
import { loading, requestedMigration } from './store';
|
||||
import { openMigrationWizard } from './console/organization-[organization]/(migration-wizard)';
|
||||
import { loading } from './store';
|
||||
|
||||
if (browser) {
|
||||
window.VERCEL_ANALYTICS_ID = import.meta.env.VERCEL_ANALYTICS_ID?.toString() ?? false;
|
||||
|
||||
@@ -7,6 +7,7 @@ import { redirect } from '@sveltejs/kit';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { LayoutLoad } from './$types';
|
||||
import { requestedMigration } from './store';
|
||||
import { parseIfString } from '$lib/helpers/object';
|
||||
|
||||
export const ssr = false;
|
||||
|
||||
@@ -14,7 +15,8 @@ export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
depends(Dependencies.ACCOUNT);
|
||||
|
||||
if (url.searchParams.has('migrate')) {
|
||||
requestedMigration.set(url.searchParams.get('migrate'));
|
||||
const migrateData = url.searchParams.get('migrate');
|
||||
requestedMigration.set(parseIfString(migrateData));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createMigrationFormStore, createMigrationProviderStore } from '$lib/stores/migration';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { requestedMigration } from '$routes/store';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import Wizard from './wizard.svelte';
|
||||
|
||||
export const formData = createMigrationFormStore();
|
||||
|
||||
export function openMigrationWizard() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
wizard.start(Wizard as any);
|
||||
const migData = get(requestedMigration);
|
||||
provider.set({
|
||||
provider: 'appwrite',
|
||||
apiKey: migData?.apiKey,
|
||||
endpoint: migData?.endpoint,
|
||||
projectID: migData?.projectId
|
||||
});
|
||||
}
|
||||
|
||||
export const selectedProject = writable(null);
|
||||
|
||||
export const provider = createMigrationProviderStore();
|
||||
@@ -0,0 +1,338 @@
|
||||
<script lang="ts">
|
||||
import { EyebrowHeading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { deepMap } from '$lib/helpers/object';
|
||||
import type { DeepKeys, WritableValue } from '$lib/helpers/types';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import {
|
||||
createMigrationFormStore,
|
||||
createMigrationProviderStore,
|
||||
providerResources
|
||||
} from '$lib/stores/migration';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
|
||||
export let formData: ReturnType<typeof createMigrationFormStore>;
|
||||
export let provider: ReturnType<typeof createMigrationProviderStore>;
|
||||
|
||||
type FormData = WritableValue<typeof formData>;
|
||||
type FormKeys = DeepKeys<FormData>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param field {string} The field to update.
|
||||
* Representes the path to the field in the formData object e.g. 'users.root'
|
||||
*/
|
||||
function handleInputChange(field: FormKeys) {
|
||||
return (event: Event) => {
|
||||
const checked = (event.target as HTMLInputElement).checked;
|
||||
// For each entry in formData, if the root is changed, change all children to the same value
|
||||
// otherwise, if a child is changed to true, change the root to true
|
||||
|
||||
const [parent, child] = field.split('.');
|
||||
if (child === 'root') {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
for (const key in data[parent]) {
|
||||
if (key !== 'root') {
|
||||
data[parent][key] = checked;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
});
|
||||
} else if (checked) {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
let report: any;
|
||||
|
||||
onMount(async () => {
|
||||
switch ($provider.provider) {
|
||||
case 'appwrite': {
|
||||
const res = await sdk.forProject.migrations.generateAppwriteReport(
|
||||
providerResources.appwrite,
|
||||
$provider.endpoint,
|
||||
$provider.projectID,
|
||||
$provider.apiKey
|
||||
);
|
||||
report = res;
|
||||
break;
|
||||
}
|
||||
case 'supabase': {
|
||||
const res = await sdk.forProject.migrations.generateSupabaseReport(
|
||||
providerResources.supabase,
|
||||
$provider.endpoint,
|
||||
$provider.apiKey,
|
||||
$provider.host,
|
||||
$provider.username,
|
||||
$provider.password,
|
||||
$provider.port
|
||||
);
|
||||
report = res;
|
||||
break;
|
||||
}
|
||||
case 'nhost': {
|
||||
const res = await sdk.forProject.migrations.generateNHostReport(
|
||||
providerResources.nhost,
|
||||
$provider.subdomain,
|
||||
$provider.region,
|
||||
$provider.adminSecret,
|
||||
$provider.database,
|
||||
$provider.username,
|
||||
$provider.password
|
||||
);
|
||||
report = res;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$: {
|
||||
wizard.setNextDisabled(!report);
|
||||
}
|
||||
|
||||
$: resources = providerResources[$provider.provider];
|
||||
</script>
|
||||
|
||||
<div class="box" style:border-radius="0.5rem">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<EyebrowHeading class="eyebrow" tag="h3" size={3}>Good to know</EyebrowHeading>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-cog" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Project settings are not imported</p>
|
||||
<p>You will need to set service and project settings manually</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-trending-up" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Keep your project plan limits in mind</p>
|
||||
<p>Make sure to have enough storage in your project plan when importing files</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-currency-dollar" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Transfer is free of charge</p>
|
||||
<p>You won't be charged for Appwrite bandwidth usage for importing data</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="buttons-list u-margin-block-start-32 u-main-end">
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={deselectAll}>Deselect all</Button>
|
||||
</li>
|
||||
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={selectAll}>Select all</Button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="u-flex u-flex-vertical u-gap-32 u-margin-block-start-16">
|
||||
{#if resources.includes('user')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.root}
|
||||
on:change={handleInputChange('users.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Users</span>
|
||||
|
||||
<span class="inline-tag">{report?.user ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all users</span>
|
||||
|
||||
{#if resources.includes('team')}
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.teams}
|
||||
on:change={handleInputChange('users.teams')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include teams</span>
|
||||
<span class="inline-tag">{report?.team ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all teams and the team memberships of your users</span>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if resources.includes('database')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.root}
|
||||
on:change={handleInputChange('databases.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Databases</span>
|
||||
<span class="inline-tag">{report?.database ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all databases, including collections, indexes and attributes</span>
|
||||
|
||||
{#if resources.includes('document')}
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.documents}
|
||||
on:change={handleInputChange('databases.documents')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include documents</span>
|
||||
<span class="inline-tag">{report?.document ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all of your documents</span>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if resources.includes('function')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.root}
|
||||
on:change={handleInputChange('functions.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Functions</span>
|
||||
<span class="inline-tag">{report?.function ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all functions and their active deployment</span>
|
||||
<ul>
|
||||
{#if resources.includes('envVar')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.env}
|
||||
on:change={handleInputChange('functions.env')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include environment variables</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all environment variables</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#if resources.includes('deployment')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.inactive}
|
||||
on:change={handleInputChange('functions.inactive')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include inactive deployments</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all deployments that are not currently active</span>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</li>
|
||||
{/if}
|
||||
{#if resources.includes('bucket') && resources.includes('file')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.storage.root}
|
||||
on:change={handleInputChange('storage.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Storage</span>
|
||||
<span class="inline-tag">
|
||||
{report?.size ? `${report.size.toFixed(2)}MB` : '...'}
|
||||
</span>
|
||||
</div>
|
||||
<div />
|
||||
|
||||
<span>
|
||||
Import all buckets <span class="inline-tag">{report?.bucket ?? '...'}</span> and
|
||||
files
|
||||
<span class="inline-tag">{report?.file ?? '...'}</span>
|
||||
</span>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
|
||||
<style lang="scss">
|
||||
.box :global(.eyebrow) {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
|
||||
.circled {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
translate: -50% -50%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-list {
|
||||
border-block-end: 1px solid hsl(var(--color-border));
|
||||
padding-block-end: 0.625rem;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.25rem 1rem;
|
||||
align-items: center;
|
||||
|
||||
ul {
|
||||
grid-column-start: 2;
|
||||
padding-block-end: 2rem;
|
||||
|
||||
li {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
|
||||
import { formData, provider } from '.';
|
||||
import ResourceForm from './resource-form.svelte';
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select data</svelte:fragment>
|
||||
<ResourceForm {provider} {formData} />
|
||||
</WizardStep>
|
||||
+14
-3
@@ -4,9 +4,12 @@
|
||||
import Wizard from '$lib/layout/wizard.svelte';
|
||||
import { migrationFormToResources } from '$lib/stores/migration';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { formData } from '.';
|
||||
import { formData, provider } from '.';
|
||||
import Step1 from './step1.svelte';
|
||||
import Step2 from './step2.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
const onExit = () => {
|
||||
formData.reset();
|
||||
@@ -14,8 +17,16 @@
|
||||
|
||||
const onFinish = async () => {
|
||||
const resources = migrationFormToResources($formData);
|
||||
console.log('resources', resources);
|
||||
// wizard.hide();
|
||||
if ($provider.provider !== 'appwrite') return;
|
||||
|
||||
const res = await sdk.forProject.migrations.migrateAppwrite(
|
||||
resources,
|
||||
$provider.endpoint,
|
||||
$provider.projectID,
|
||||
$provider.apiKey
|
||||
);
|
||||
console.log('appwrite', res);
|
||||
invalidate(Dependencies.MIGRATIONS);
|
||||
};
|
||||
|
||||
onDestroy(onExit);
|
||||
@@ -21,6 +21,7 @@
|
||||
import { CommandCenter, registerCommands } from '$lib/commandCenter';
|
||||
import { AI, Organizations } from '$lib/commandCenter/panels';
|
||||
import { addSubPanel } from '$lib/commandCenter/subPanels';
|
||||
import { openMigrationWizard } from './(migration-wizard)';
|
||||
|
||||
$: $registerCommands([
|
||||
{
|
||||
@@ -108,6 +109,10 @@
|
||||
$log.data = null;
|
||||
$log.func = null;
|
||||
}
|
||||
|
||||
$: if ($requestedMigration) {
|
||||
openMigrationWizard();
|
||||
}
|
||||
</script>
|
||||
|
||||
<CommandCenter />
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import { createMigrationFormStore } from '$lib/stores/migration';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { writable } from 'svelte/store';
|
||||
import Wizard from './wizard.svelte';
|
||||
|
||||
export const formData = createMigrationFormStore();
|
||||
|
||||
export function openMigrationWizard() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
wizard.start(Wizard as any);
|
||||
}
|
||||
|
||||
export const selectedProject = writable(null);
|
||||
@@ -1,296 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { EyebrowHeading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { deepMap } from '$lib/helpers/object';
|
||||
import type { DeepKeys, WritableValue } from '$lib/helpers/types';
|
||||
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { migrationFormToResources } from '$lib/stores/migration';
|
||||
import { formData } from '.';
|
||||
|
||||
type FormData = WritableValue<typeof formData>;
|
||||
type FormKeys = DeepKeys<FormData>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param field {string} The field to update.
|
||||
* Representes the path to the field in the formData object e.g. 'users.root'
|
||||
*/
|
||||
function handleInputChange(field: FormKeys) {
|
||||
return (event: Event) => {
|
||||
const checked = (event.target as HTMLInputElement).checked;
|
||||
// For each entry in formData, if the root is changed, change all children to the same value
|
||||
// otherwise, if a child is changed to true, change the root to true
|
||||
|
||||
const [parent, child] = field.split('.');
|
||||
if (child === 'root') {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
for (const key in data[parent]) {
|
||||
if (key !== 'root') {
|
||||
data[parent][key] = checked;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
});
|
||||
} else if (checked) {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
let report: any;
|
||||
|
||||
onMount(async () => {
|
||||
const resources = migrationFormToResources($formData);
|
||||
|
||||
// switch ($provider.provider) {
|
||||
// case 'appwrite': {
|
||||
// const res = await sdk.forProject.migrations.generateAppwriteReport(
|
||||
// resources,
|
||||
// $provider.endpoint,
|
||||
// $provider.projectID,
|
||||
// $provider.apiKey
|
||||
// );
|
||||
// report = res;
|
||||
// }
|
||||
// }
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select data</svelte:fragment>
|
||||
<div class="box" style:border-radius="0.5rem">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<EyebrowHeading class="eyebrow" tag="h3" size={3}>Good to know</EyebrowHeading>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-cog" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Project settings are not imported</p>
|
||||
<p>You will need to set service and project settings manually</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-trending-up" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Keep your project plan limits in mind</p>
|
||||
<p>
|
||||
Make sure to have enough storage in your project plan when importing files
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-currency-dollar" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Transfer is free of charge</p>
|
||||
<p>You won’t be charged for Appwrite bandwidth usage for importing data</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="buttons-list u-margin-block-start-32 u-main-end">
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={deselectAll}>Deselect all</Button>
|
||||
</li>
|
||||
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={selectAll}>Select all</Button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="u-flex u-flex-vertical u-gap-32 u-margin-block-start-16">
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.root}
|
||||
on:change={handleInputChange('users.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Users</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.user}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all users</span>
|
||||
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.teams}
|
||||
on:change={handleInputChange('users.teams')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include teams</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.team}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all teams and the team memberships of your users</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.root}
|
||||
on:change={handleInputChange('databases.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Databases</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.database}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all databases, including collections, indexes and attributes</span>
|
||||
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.documents}
|
||||
on:change={handleInputChange('databases.documents')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include documents</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.document}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all of your documents</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.root}
|
||||
on:change={handleInputChange('functions.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Functions</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.function}</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all functions and their active deployment</span>
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.env}
|
||||
on:change={handleInputChange('functions.env')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include environment variables</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all environment variables</span>
|
||||
</li>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.inactive}
|
||||
on:change={handleInputChange('functions.inactive')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include inactive deployments</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all deployments that are not currently active</span>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.storage.root}
|
||||
on:change={handleInputChange('storage.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Storage</span>
|
||||
{#if report}
|
||||
<span class="inline-tag">{report.size.toFixed(2)}MB</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div />
|
||||
{#if report}
|
||||
<span>Import all buckets ({report.bucket}) and files ({report.file})</span>
|
||||
{:else}
|
||||
<span>Import all buckets and files</span>
|
||||
{/if}
|
||||
</li>
|
||||
</ul>
|
||||
</WizardStep>
|
||||
|
||||
<style lang="scss">
|
||||
.box :global(.eyebrow) {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
|
||||
.circled {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
translate: -50% -50%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-list {
|
||||
border-block-end: 1px solid hsl(var(--color-border));
|
||||
padding-block-end: 0.625rem;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.25rem 1rem;
|
||||
align-items: center;
|
||||
|
||||
ul {
|
||||
grid-column-start: 2;
|
||||
padding-block-end: 2rem;
|
||||
|
||||
li {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
import { newOrgModal, newMemberModal } from '$lib/stores/organization';
|
||||
import CreateMember from './createMember.svelte';
|
||||
import Create from '../createOrganization.svelte';
|
||||
import { openMigrationWizard } from './(migration-wizard)';
|
||||
import { openMigrationWizard } from '../(migration-wizard)';
|
||||
import { requestedMigration } from '$routes/store';
|
||||
|
||||
$: if ($requestedMigration) {
|
||||
|
||||
@@ -1,91 +1,13 @@
|
||||
import {
|
||||
PUBLIC_MOCK_APIKEY,
|
||||
PUBLIC_MOCK_ENDPOINT,
|
||||
PUBLIC_MOCK_PROJECTID,
|
||||
PUBLIC_SUPABASE_TEST_ENDPOINT,
|
||||
PUBLIC_SUPABASE_TEST_HOST,
|
||||
PUBLIC_SUPABASE_TEST_KEY,
|
||||
PUBLIC_SUPABASE_TEST_PASSWORD,
|
||||
PUBLIC_SUPABASE_TEST_USERNAME,
|
||||
PUBLIC_SUPABASE_TEST_PORT,
|
||||
PUBLIC_NHOST_TEST_SUBDOMAIN,
|
||||
PUBLIC_NHOST_TEST_REGION,
|
||||
PUBLIC_NHOST_TEST_SECRET,
|
||||
PUBLIC_NHOST_TEST_DATABASE,
|
||||
PUBLIC_NHOST_TEST_USERNAME,
|
||||
PUBLIC_NHOST_TEST_PASSWORD
|
||||
} from '$env/static/public';
|
||||
import { createMigrationFormStore } from '$lib/stores/migration';
|
||||
import { createMigrationFormStore, createMigrationProviderStore } from '$lib/stores/migration';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { writable } from 'svelte/store';
|
||||
import Wizard from './wizard.svelte';
|
||||
|
||||
type AppwriteInput = {
|
||||
provider: 'appwrite';
|
||||
endpoint?: string;
|
||||
projectID?: string;
|
||||
apiKey?: string;
|
||||
};
|
||||
|
||||
type FirebaseInput = {
|
||||
provider: 'firebase';
|
||||
serviceAccount?: string;
|
||||
};
|
||||
|
||||
type SupabaseInput = {
|
||||
provider: 'supabase';
|
||||
host?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
endpoint?: string;
|
||||
apiKey?: string;
|
||||
port?: number;
|
||||
};
|
||||
|
||||
type NhostInput = {
|
||||
provider: 'nhost';
|
||||
region?: string;
|
||||
subdomain?: string;
|
||||
database?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
adminSecret?: string;
|
||||
};
|
||||
|
||||
type ProviderInput = AppwriteInput | NhostInput | SupabaseInput | FirebaseInput;
|
||||
|
||||
const initialProvider: ProviderInput = { provider: 'appwrite' };
|
||||
// const mockProvider: ProviderInput = {
|
||||
// provider: 'appwrite',
|
||||
// endpoint: PUBLIC_MOCK_ENDPOINT,
|
||||
// apiKey: PUBLIC_MOCK_APIKEY,
|
||||
// projectID: PUBLIC_MOCK_PROJECTID
|
||||
// };
|
||||
// const mockProvider: ProviderInput = {
|
||||
// provider: 'supabase',
|
||||
// endpoint: PUBLIC_SUPABASE_TEST_ENDPOINT,
|
||||
// apiKey: PUBLIC_SUPABASE_TEST_KEY,
|
||||
// host: PUBLIC_SUPABASE_TEST_HOST,
|
||||
// port: Number(PUBLIC_SUPABASE_TEST_PORT),
|
||||
// username: PUBLIC_SUPABASE_TEST_USERNAME,
|
||||
// password: PUBLIC_SUPABASE_TEST_PASSWORD
|
||||
// };
|
||||
const mockProvider: ProviderInput = {
|
||||
provider: 'nhost',
|
||||
subdomain: PUBLIC_NHOST_TEST_SUBDOMAIN,
|
||||
region: PUBLIC_NHOST_TEST_REGION,
|
||||
adminSecret: PUBLIC_NHOST_TEST_SECRET,
|
||||
database: PUBLIC_NHOST_TEST_DATABASE,
|
||||
username: PUBLIC_NHOST_TEST_USERNAME,
|
||||
password: PUBLIC_NHOST_TEST_PASSWORD
|
||||
};
|
||||
|
||||
export const provider = writable<ProviderInput>({ ...mockProvider });
|
||||
export const provider = createMigrationProviderStore();
|
||||
|
||||
export const formData = createMigrationFormStore();
|
||||
|
||||
export const resetImportStores = () => {
|
||||
provider.set({ ...mockProvider });
|
||||
provider.changeProvider('appwrite');
|
||||
formData.reset();
|
||||
};
|
||||
|
||||
|
||||
@@ -1,333 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { EyebrowHeading } from '$lib/components';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { deepMap } from '$lib/helpers/object';
|
||||
import type { DeepKeys, WritableValue } from '$lib/helpers/types';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
import { providerResources } from '$lib/stores/migration';
|
||||
import ResourceForm from '$routes/console/(migration-wizard)/resource-form.svelte';
|
||||
import { formData, provider } from '.';
|
||||
|
||||
type FormData = WritableValue<typeof formData>;
|
||||
type FormKeys = DeepKeys<FormData>;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param field {string} The field to update.
|
||||
* Representes the path to the field in the formData object e.g. 'users.root'
|
||||
*/
|
||||
function handleInputChange(field: FormKeys) {
|
||||
return (event: Event) => {
|
||||
const checked = (event.target as HTMLInputElement).checked;
|
||||
// For each entry in formData, if the root is changed, change all children to the same value
|
||||
// otherwise, if a child is changed to true, change the root to true
|
||||
|
||||
const [parent, child] = field.split('.');
|
||||
if (child === 'root') {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
for (const key in data[parent]) {
|
||||
if (key !== 'root') {
|
||||
data[parent][key] = checked;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
});
|
||||
} else if (checked) {
|
||||
formData.update((data) => {
|
||||
data[parent].root = checked;
|
||||
return data;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function deselectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return false;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
$formData = deepMap($formData, (v) => {
|
||||
if (typeof v === 'boolean') {
|
||||
return true;
|
||||
}
|
||||
return v;
|
||||
});
|
||||
}
|
||||
|
||||
let report: any;
|
||||
|
||||
onMount(async () => {
|
||||
switch ($provider.provider) {
|
||||
case 'appwrite': {
|
||||
const res = await sdk.forProject.migrations.generateAppwriteReport(
|
||||
providerResources.appwrite,
|
||||
$provider.endpoint,
|
||||
$provider.projectID,
|
||||
$provider.apiKey
|
||||
);
|
||||
report = res;
|
||||
break;
|
||||
}
|
||||
case 'supabase': {
|
||||
const res = await sdk.forProject.migrations.generateSupabaseReport(
|
||||
providerResources.supabase,
|
||||
$provider.endpoint,
|
||||
$provider.apiKey,
|
||||
$provider.host,
|
||||
$provider.username,
|
||||
$provider.password,
|
||||
$provider.port
|
||||
);
|
||||
report = res;
|
||||
break;
|
||||
}
|
||||
case 'nhost': {
|
||||
const res = await sdk.forProject.migrations.generateNHostReport(
|
||||
providerResources.nhost,
|
||||
$provider.subdomain,
|
||||
$provider.region,
|
||||
$provider.adminSecret,
|
||||
$provider.database,
|
||||
$provider.username,
|
||||
$provider.password
|
||||
);
|
||||
report = res;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
$: resources = providerResources[$provider.provider];
|
||||
</script>
|
||||
|
||||
<WizardStep nextDisabled={!report}>
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select data</svelte:fragment>
|
||||
<div class="box" style:border-radius="0.5rem">
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<EyebrowHeading class="eyebrow" tag="h3" size={3}>Good to know</EyebrowHeading>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-cog" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Project settings are not imported</p>
|
||||
<p>You will need to set service and project settings manually</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-trending-up" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Keep your project plan limits in mind</p>
|
||||
<p>
|
||||
Make sure to have enough storage in your project plan when importing files
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16">
|
||||
<div class="circled">
|
||||
<i class="icon-currency-dollar" />
|
||||
</div>
|
||||
<div>
|
||||
<p class="u-bold">Transfer is free of charge</p>
|
||||
<p>You won't be charged for Appwrite bandwidth usage for importing data</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="buttons-list u-margin-block-start-32 u-main-end">
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={deselectAll}>Deselect all</Button>
|
||||
</li>
|
||||
|
||||
<li class="buttons-list-item">
|
||||
<Button text on:click={selectAll}>Select all</Button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="u-flex u-flex-vertical u-gap-32 u-margin-block-start-16">
|
||||
{#if resources.includes('user')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.root}
|
||||
on:change={handleInputChange('users.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Users</span>
|
||||
|
||||
<span class="inline-tag">{report?.user ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all users</span>
|
||||
|
||||
{#if resources.includes('team')}
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.users.teams}
|
||||
on:change={handleInputChange('users.teams')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include teams</span>
|
||||
<span class="inline-tag">{report?.team ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all teams and the team memberships of your users</span>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if resources.includes('database')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.root}
|
||||
on:change={handleInputChange('databases.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Databases</span>
|
||||
<span class="inline-tag">{report?.database ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all databases, including collections, indexes and attributes</span>
|
||||
|
||||
{#if resources.includes('document')}
|
||||
<ul>
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.databases.documents}
|
||||
on:change={handleInputChange('databases.documents')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include documents</span>
|
||||
<span class="inline-tag">{report?.document ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all of your documents</span>
|
||||
</li>
|
||||
</ul>
|
||||
{/if}
|
||||
</li>
|
||||
{/if}
|
||||
|
||||
{#if resources.includes('function')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.root}
|
||||
on:change={handleInputChange('functions.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Functions</span>
|
||||
<span class="inline-tag">{report?.function ?? '...'}</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all functions and their active deployment</span>
|
||||
<ul>
|
||||
{#if resources.includes('envVar')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.env}
|
||||
on:change={handleInputChange('functions.env')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include environment variables</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all environment variables</span>
|
||||
</li>
|
||||
{/if}
|
||||
{#if resources.includes('deployment')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.functions.inactive}
|
||||
on:change={handleInputChange('functions.inactive')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Include inactive deployments</span>
|
||||
</div>
|
||||
<div />
|
||||
<span>Import all deployments that are not currently active</span>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
</li>
|
||||
{/if}
|
||||
{#if resources.includes('bucket') && resources.includes('file')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
bind:checked={$formData.storage.root}
|
||||
on:change={handleInputChange('storage.root')} />
|
||||
<div class="u-flex u-gap-4">
|
||||
<span class="u-bold">Storage</span>
|
||||
<span class="inline-tag">
|
||||
{report?.size ? `${report.size.toFixed(2)}MB` : '...'}
|
||||
</span>
|
||||
</div>
|
||||
<div />
|
||||
|
||||
<span>
|
||||
Import all buckets <span class="inline-tag">{report?.bucket ?? '...'}</span> and
|
||||
files
|
||||
<span class="inline-tag">{report?.file ?? '...'}</span>
|
||||
</span>
|
||||
</li>
|
||||
{/if}
|
||||
</ul>
|
||||
<ResourceForm {formData} {provider} />
|
||||
</WizardStep>
|
||||
|
||||
<style lang="scss">
|
||||
.box :global(.eyebrow) {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
|
||||
.circled {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
border-radius: 100%;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
position: relative;
|
||||
|
||||
i {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
translate: -50% -50%;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons-list {
|
||||
border-block-end: 1px solid hsl(var(--color-border));
|
||||
padding-block-end: 0.625rem;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
gap: 0.25rem 1rem;
|
||||
align-items: center;
|
||||
|
||||
ul {
|
||||
grid-column-start: 2;
|
||||
padding-block-end: 2rem;
|
||||
|
||||
li {
|
||||
margin-block-start: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import { openImportWizard } from './(import)';
|
||||
import Details from './details.svelte';
|
||||
import ExportModal from './exportModal.svelte';
|
||||
import { registerCommands } from '$lib/commandCenter';
|
||||
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
|
||||
|
||||
export let data;
|
||||
let details: (typeof data.migrations)[number] | null = null;
|
||||
@@ -48,17 +48,24 @@
|
||||
$: $registerCommands([
|
||||
{
|
||||
label: 'Import data',
|
||||
icon: 'upload',
|
||||
icon: 'download',
|
||||
keys: ['i', 'd'],
|
||||
callback: openImportWizard
|
||||
callback: openImportWizard,
|
||||
group: 'migrations'
|
||||
},
|
||||
{
|
||||
label: 'Export data',
|
||||
icon: 'download',
|
||||
icon: 'upload',
|
||||
keys: ['e', 'd'],
|
||||
callback: () => (showExport = true)
|
||||
callback: () => (showExport = true),
|
||||
group: 'migrations'
|
||||
}
|
||||
]);
|
||||
|
||||
$: $updateCommandGroupRanks((prev) => ({
|
||||
...prev,
|
||||
migrations: 100
|
||||
}));
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import { Alert, Modal } from '$lib/components';
|
||||
import { Button, InputText, InputTextarea } from '$lib/elements/forms';
|
||||
import { getFormData } from '$lib/helpers/form';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
|
||||
export let show = false;
|
||||
let submitted = false;
|
||||
@@ -39,7 +41,7 @@
|
||||
return endpoint;
|
||||
};
|
||||
|
||||
const onSubmit = (e: SubmitEvent) => {
|
||||
const onSubmit = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
submitted = true;
|
||||
|
||||
@@ -56,10 +58,39 @@
|
||||
}
|
||||
|
||||
const currEndpoint = getCurrentEndpoint();
|
||||
// URI encode the current endpoint, so that it can be passed as a query string
|
||||
const encodedCurrEndpoint = encodeURIComponent(currEndpoint);
|
||||
// Create API key
|
||||
const { secret } = await sdk.forConsole.projects.createKey(
|
||||
$project.$id,
|
||||
`[AUTO-GENERATED] Migration ${new Date().toISOString()}`,
|
||||
|
||||
const dest = `${removeTrailingSlash(endpoint)}/?migrate=${encodedCurrEndpoint}`;
|
||||
[
|
||||
'users.read',
|
||||
'teams.read',
|
||||
'databases.read',
|
||||
'collections.read',
|
||||
'attributes.read',
|
||||
'indexes.read',
|
||||
'documents.read',
|
||||
'files.read',
|
||||
'buckets.read',
|
||||
'functions.read',
|
||||
'execution.read',
|
||||
'locale.read',
|
||||
'avatars.read',
|
||||
'health.read'
|
||||
],
|
||||
undefined
|
||||
);
|
||||
|
||||
const migrationData = {
|
||||
endpoint: currEndpoint,
|
||||
projectId: $project.$id,
|
||||
apiKey: secret
|
||||
};
|
||||
|
||||
const dest = `${removeTrailingSlash(endpoint)}/?migrate=${encodeURIComponent(
|
||||
JSON.stringify(migrationData)
|
||||
)}`;
|
||||
window.location.href = dest;
|
||||
};
|
||||
</script>
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ export const loading = writable(true);
|
||||
export const organizations = derived(page, ($page) => {
|
||||
return $page.data.organizations as Models.TeamList<Models.Preferences>;
|
||||
});
|
||||
export const requestedMigration = writable<string | null>(null);
|
||||
export const requestedMigration = writable<Record<string, any> | null>(null);
|
||||
|
||||
Reference in New Issue
Block a user