mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: add domain flow (WIP)
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
export let autocomplete = false;
|
||||
export let maxlength: number = null;
|
||||
|
||||
export let error: string;
|
||||
export let error: string = null;
|
||||
|
||||
const handleInvalid = (event: Event) => {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
import Table from './table.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
$: console.log(data.domains);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
|
||||
+128
-145
@@ -5,120 +5,78 @@
|
||||
import { Wizard } from '$lib/layout';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import {
|
||||
Fieldset,
|
||||
Layout,
|
||||
Tooltip,
|
||||
Icon,
|
||||
Input,
|
||||
Divider,
|
||||
Typography,
|
||||
Card
|
||||
} from '@appwrite.io/pink-svelte';
|
||||
import { Fieldset, Layout, Tooltip, Icon, Input, Alert } from '@appwrite.io/pink-svelte';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sortBranches } from '$lib/stores/vcs';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { consoleVariables, protocol } from '$routes/(console)/store';
|
||||
import { IconGlobeAlt, IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { LabelCard } from '$lib/components';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { StatusCode } from '@appwrite.io/console';
|
||||
import VerificationFieldset from './verificationFieldset.svelte';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import { statusCodeOptions } from '$lib/stores/domains';
|
||||
import ConnectRepoModal from '../../../(components)/connectRepoModal.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const backPage = `${base}/project-${$page.params.project}/sites/site-${$page.params.site}/domains`;
|
||||
|
||||
export let data;
|
||||
|
||||
let formComponent: Form;
|
||||
let isSubmitting = writable(false);
|
||||
|
||||
let showConnectRepo = false;
|
||||
|
||||
let behaviour: 'REDIRECT' | 'BRANCH' | 'ACTIVE' = 'ACTIVE';
|
||||
let step: 'add' | 'verify' = 'add';
|
||||
let domainName = '';
|
||||
let redirect: string = null;
|
||||
let statusCode: number = null;
|
||||
let branch = null;
|
||||
|
||||
let domainData: Domain;
|
||||
let selectedTab: 'cname' | 'nameserver';
|
||||
|
||||
const redirectOptions = data.domains.rules
|
||||
const redirectOptions = data.rules.rules
|
||||
.filter((d) => !d.domain.endsWith($consoleVariables._APP_DOMAIN_SITES))
|
||||
.map((domain) => ({
|
||||
label: domain.domain,
|
||||
value: domain.domain
|
||||
}));
|
||||
|
||||
async function addDomain() {
|
||||
const isNewDomain =
|
||||
data.domains.rules.findIndex((rule) => rule.domain === domainName) === -1;
|
||||
try {
|
||||
if (isNewDomain && isCloud) {
|
||||
domainData = await sdk.forConsole.domains.create($organization.$id, domainName);
|
||||
}
|
||||
onMount(() => {
|
||||
if (
|
||||
$page.url.searchParams.has('connectRepo') &&
|
||||
$page.url.searchParams.get('connectRepo') === 'true'
|
||||
) {
|
||||
showConnectRepo = true;
|
||||
}
|
||||
});
|
||||
|
||||
async function addDomain() {
|
||||
try {
|
||||
if (behaviour === 'BRANCH') {
|
||||
await sdk.forProject.proxy.createSiteRule(domainName, $page.params.site, branch);
|
||||
} else if (behaviour === 'REDIRECT') {
|
||||
const sc = Object.values(StatusCode).find((code) => parseInt(code) === statusCode);
|
||||
console.log(statusCode, sc);
|
||||
await sdk.forProject.proxy.createRedirectRule(domainName, $protocol + redirect, sc);
|
||||
} else if (behaviour === 'ACTIVE') {
|
||||
await sdk.forProject.proxy.createSiteRule(domainName, $page.params.site);
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Domain added successfully'
|
||||
});
|
||||
await goto(backPage);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
await invalidate(Dependencies.SITES_DOMAINS);
|
||||
} catch (error) {
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$: isVerified = domainData?.nameservers
|
||||
? domainData?.nameservers.toLocaleLowerCase() === 'appwrite'
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<Wizard title="Add custom domain" href={backPage} column columnSize="s" hideFooter={step === 'add'}>
|
||||
<Form onSubmit={() => (step = 'verify')}>
|
||||
{#if step === 'add'}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Layout.Grid columns={3} columnsXS={1}>
|
||||
<LabelCard value="ACTIVE" bind:group={behaviour} title="Active deployment">
|
||||
Point this domain to the latest deployed version.
|
||||
</LabelCard>
|
||||
<Tooltip disabled={!!data.site?.providerRepositoryId}>
|
||||
<div>
|
||||
<LabelCard
|
||||
value="BRANCH"
|
||||
bind:group={behaviour}
|
||||
title="Git branch"
|
||||
disabled={!data.site?.providerRepositoryId}>
|
||||
Point this domain to a specific branch in your repository.
|
||||
</LabelCard>
|
||||
</div>
|
||||
<svelte:fragment slot="tooltip">
|
||||
No repository connected to your site.
|
||||
</svelte:fragment>
|
||||
</Tooltip>
|
||||
<LabelCard value="REDIRECT" bind:group={behaviour} title="Redirect">
|
||||
Forward all traffic from this domain to another URL.
|
||||
</LabelCard>
|
||||
</Layout.Grid>
|
||||
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
<!-- <Input.ComboBox
|
||||
<Wizard title="Add custom domain" href={backPage} column columnSize="s">
|
||||
<Form bind:this={formComponent} onSubmit={addDomain} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Domain">
|
||||
<!-- <Input.ComboBox
|
||||
label="Domain"
|
||||
id="domain"
|
||||
name="domain"
|
||||
@@ -129,93 +87,118 @@
|
||||
}))}
|
||||
required
|
||||
placeholder="appwrite.example.com" /> -->
|
||||
<InputDomain
|
||||
label="Domain"
|
||||
id="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
placeholder="appwrite.example.com" />
|
||||
<InputDomain
|
||||
label="Domain"
|
||||
id="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
placeholder="appwrite.example.com" />
|
||||
</Fieldset>
|
||||
|
||||
{#if behaviour === 'BRANCH'}
|
||||
{#if data.site?.providerRepositoryId}
|
||||
{@const sortedBranches = sortBranches(data.branches.branches)}
|
||||
{@const options = sortedBranches.map((branch) => ({
|
||||
label: branch.name,
|
||||
value: branch.name
|
||||
}))}
|
||||
<Layout.Stack gap="s">
|
||||
<InputSelect
|
||||
{options}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={branch}
|
||||
placeholder="Select branch" />
|
||||
{#if !data.branches?.total}
|
||||
<Input.Helper state="default">
|
||||
No branches found in the selected repository. Create a
|
||||
branch to see it here.
|
||||
</Input.Helper>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
{:else if behaviour === 'REDIRECT'}
|
||||
<Layout.Grid columns={3} columnsXS={1}>
|
||||
<LabelCard value="ACTIVE" bind:group={behaviour} title="Active deployment">
|
||||
Point this domain to the latest deployed version.
|
||||
</LabelCard>
|
||||
<LabelCard value="BRANCH" bind:group={behaviour} title="Git branch">
|
||||
Point this domain to a specific branch in your repository.
|
||||
</LabelCard>
|
||||
<LabelCard value="REDIRECT" bind:group={behaviour} title="Redirect">
|
||||
Forward all traffic from this domain to another URL.
|
||||
</LabelCard>
|
||||
</Layout.Grid>
|
||||
|
||||
{#if behaviour === 'BRANCH'}
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
{#if data.site?.providerRepositoryId}
|
||||
{@const sortedBranches = sortBranches(data.branches.branches)}
|
||||
{@const options = sortedBranches.map((branch) => ({
|
||||
label: branch.name,
|
||||
value: branch.name
|
||||
}))}
|
||||
<Layout.Stack gap="s">
|
||||
<InputSelect
|
||||
{options}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={branch}
|
||||
placeholder="Select branch" />
|
||||
{#if !data.branches?.total}
|
||||
<Input.Helper state="default">
|
||||
No branches found in the selected repository. Create a
|
||||
branch to see it here.
|
||||
</Input.Helper>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
<InputSelect
|
||||
label="Redirect to"
|
||||
id="redirect"
|
||||
placeholder="Select domain"
|
||||
options={redirectOptions}
|
||||
bind:value={redirect}
|
||||
required>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Redirect this domain. Domains added to your project will be
|
||||
listed here.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputSelect>
|
||||
<InputSelect
|
||||
options={statusCodeOptions}
|
||||
label="Status code"
|
||||
id="code"
|
||||
disabled
|
||||
options={[{ label: 'main', value: 'main' }]}
|
||||
label="Production branch"
|
||||
id="branch"
|
||||
required
|
||||
bind:value={statusCode}
|
||||
placeholder="Select status code" />
|
||||
value="main"
|
||||
placeholder="Select branch" />
|
||||
<Alert.Inline title=" There is no repository connected to your site">
|
||||
<Layout.Stack>
|
||||
<p>
|
||||
The domain will be connected to your active deployment.
|
||||
Connect your Git repository to link a production branch.
|
||||
</p>
|
||||
<div>
|
||||
<Button compact on:click={() => (showConnectRepo = true)}>
|
||||
Connect repository
|
||||
</Button>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
</Alert.Inline>
|
||||
{/if}
|
||||
<Divider />
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button submit>Add</Button>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
</Layout.Stack>
|
||||
{:else if step === 'verify'}
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card.Base radius="s" padding="s">
|
||||
<Layout.Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="xs">
|
||||
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
|
||||
|
||||
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
|
||||
{domainName}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={() => (step = 'add')}>Change</Button>
|
||||
{:else if behaviour === 'REDIRECT'}
|
||||
<Fieldset legend="Settings">
|
||||
<Layout.Stack gap="xl">
|
||||
<InputSelect
|
||||
label="Redirect to"
|
||||
id="redirect"
|
||||
placeholder="Select domain"
|
||||
options={redirectOptions}
|
||||
bind:value={redirect}
|
||||
required>
|
||||
<Tooltip slot="info">
|
||||
<Icon icon={IconInfo} size="s" />
|
||||
<span slot="tooltip">
|
||||
Redirect this domain. Domains added to your project will be
|
||||
listed here.
|
||||
</span>
|
||||
</Tooltip>
|
||||
</InputSelect>
|
||||
<InputSelect
|
||||
options={statusCodeOptions}
|
||||
label="Status code"
|
||||
id="code"
|
||||
required
|
||||
bind:value={statusCode}
|
||||
placeholder="Select status code" />
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<VerificationFieldset domain={domainName} verified={isVerified} bind:selectedTab />
|
||||
</Layout.Stack>
|
||||
{/if}
|
||||
</Fieldset>
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Form>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary href={backPage}>Cancel</Button>
|
||||
<Button on:click={addDomain}>Verify</Button>
|
||||
<Button on:click={() => formComponent.triggerSubmit()} bind:disabled={$isSubmitting}>
|
||||
Add
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Wizard>
|
||||
|
||||
{#if showConnectRepo}
|
||||
<ConnectRepoModal
|
||||
bind:show={showConnectRepo}
|
||||
site={data.site}
|
||||
onlyExisting
|
||||
callbackState={{ connectRepo: 'true' }} />
|
||||
{/if}
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@ import { Dependencies } from '$lib/constants.js';
|
||||
|
||||
export const load = async ({ parent, depends }) => {
|
||||
const { site } = await parent();
|
||||
depends(Dependencies.DOMAINS);
|
||||
depends(Dependencies.DOMAINS, Dependencies.SITES_DOMAINS);
|
||||
|
||||
const [domains, installations] = await Promise.all([
|
||||
const [rules, installations] = await Promise.all([
|
||||
sdk.forProject.proxy.listRules([
|
||||
Query.equal('type', RuleType.DEPLOYMENT),
|
||||
Query.equal('trigger', RuleTrigger.MANUAL)
|
||||
@@ -17,7 +17,7 @@ export const load = async ({ parent, depends }) => {
|
||||
|
||||
return {
|
||||
site,
|
||||
domains,
|
||||
rules,
|
||||
installations,
|
||||
branches:
|
||||
site?.installationId && site?.providerRepositoryId
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { IconGlobeAlt } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Card, Icon, Layout, Typography } from '@appwrite.io/pink-svelte';
|
||||
import VerificationFieldset from './verificationFieldset.svelte';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { isCloud } from '$lib/system';
|
||||
import { page } from '$app/stores';
|
||||
|
||||
let selectedTab: 'cname' | 'nameserver';
|
||||
|
||||
async function back() {}
|
||||
|
||||
async function addDomain() {
|
||||
const isNewDomain =
|
||||
data.domains.rules.findIndex((rule) => rule.domain === $page.params.domain) === -1;
|
||||
try {
|
||||
if (isNewDomain && isCloud) {
|
||||
domainData = await sdk.forConsole.domains.create(
|
||||
$organization.$id,
|
||||
$page.params.domain
|
||||
);
|
||||
}
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Domain added successfully'
|
||||
});
|
||||
await goto(backPage);
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
await invalidate(Dependencies.SITES_DOMAINS);
|
||||
} catch (error) {
|
||||
await invalidate(Dependencies.DOMAINS);
|
||||
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
$: isVerified = domainData?.nameservers
|
||||
? domainData?.nameservers.toLocaleLowerCase() === 'appwrite'
|
||||
: undefined;
|
||||
</script>
|
||||
|
||||
<Layout.Stack gap="xxl">
|
||||
<Card.Base radius="s" padding="s">
|
||||
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center" gap="xs">
|
||||
<Layout.Stack direction="row" alignItems="center" gap="xs">
|
||||
<Icon icon={IconGlobeAlt} color="--fgcolor-neutral-primary" />
|
||||
|
||||
<Typography.Text variation="m-500" color="--fgcolor-neutral-primary">
|
||||
{$page.params.domain}
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
<Button secondary on:click={back}>Change</Button>
|
||||
</Layout.Stack>
|
||||
</Card.Base>
|
||||
|
||||
<VerificationFieldset domain={$page.params.domain} verified={isVerified} bind:selectedTab />
|
||||
</Layout.Stack>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Dependencies } from '$lib/constants.js';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { Domain, DomainsList } from '$lib/sdk/domains.js';
|
||||
|
||||
export const load = async ({ parent, depends, params }) => {
|
||||
const { site } = await parent();
|
||||
depends(Dependencies.DOMAINS);
|
||||
|
||||
let domain: Domain;
|
||||
let domainsList: DomainsList;
|
||||
if (isCloud) {
|
||||
[domain, domainsList] = await Promise.all([
|
||||
sdk.forConsole.domains.get(params.domain),
|
||||
sdk.forConsole.domains.list()
|
||||
]);
|
||||
}
|
||||
|
||||
return {
|
||||
site,
|
||||
domain,
|
||||
domainsList
|
||||
};
|
||||
};
|
||||
@@ -1,53 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Copy } from '$lib/components';
|
||||
import { Link } from '$lib/elements';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
|
||||
import { Badge, Layout, Typography, Table, Fieldset, Icon } from '@appwrite.io/pink-svelte';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export let domain: Domain | Models.ProxyRule;
|
||||
</script>
|
||||
|
||||
<Fieldset legend="Verification">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="xl">
|
||||
<Layout.Stack gap="s">
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Typography.Text variant="l-500">{domain?.domain}</Typography.Text>
|
||||
<Badge variant="secondary" type="warning" content="Pending verification" />
|
||||
</Layout.Stack>
|
||||
<Typography.Text variant="m-400">
|
||||
Add the following nameservers on your DNS provider. Note that DNS changes may
|
||||
take time to propagate fully.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
|
||||
<Table.Root columns={3} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
<Table.Header.Cell {root}>Type</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Name</Table.Header.Cell>
|
||||
<Table.Header.Cell {root}>Value</Table.Header.Cell>
|
||||
</svelte:fragment>
|
||||
<Table.Row.Base {root}>
|
||||
<Table.Cell {root}>CNAME</Table.Cell>
|
||||
<Table.Cell {root}>{domain?.domain}</Table.Cell>
|
||||
<Table.Cell {root}>
|
||||
<Copy value={globalThis?.location?.origin}
|
||||
>{globalThis?.location?.origin}</Copy>
|
||||
</Table.Cell>
|
||||
</Table.Row.Base>
|
||||
</Table.Root>
|
||||
<Layout.Stack gap="s" direction="row" alignItems="center">
|
||||
<Icon icon={IconInfo} size="s" color="--fgcolor-neutral-secondary" />
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-secondary">
|
||||
A list of all domain providers and their DNS setting is available <Link
|
||||
variant="muted"
|
||||
href="#">here</Link
|
||||
>.
|
||||
</Typography.Text>
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
<slot />
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
+1
-2
@@ -6,12 +6,11 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Domain } from '$lib/sdk/domains';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import CnameTable from '$lib/components/domains/cnameTable.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let selectedDomain: Domain | Models.ProxyRule;
|
||||
export let selectedDomain: Models.ProxyRule;
|
||||
|
||||
let error = null;
|
||||
async function retryDomain() {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const columns = writable<Column[]>([
|
||||
title: 'Domain',
|
||||
type: 'string',
|
||||
format: 'string',
|
||||
width: { min: 200, max: 600 }
|
||||
width: { min: 200, max: 700 }
|
||||
},
|
||||
|
||||
{
|
||||
@@ -20,6 +20,6 @@ export const columns = writable<Column[]>([
|
||||
id: 'deploymentVcsProviderBranch',
|
||||
title: 'Production branch',
|
||||
type: 'string',
|
||||
width: { min: 90, max: 150 }
|
||||
width: { min: 90, max: 200 }
|
||||
}
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user