feat: add domain flow (WIP)

This commit is contained in:
Arman
2025-03-20 15:45:41 +01:00
parent 3fe4938493
commit 1641ea5fac
11 changed files with 224 additions and 208 deletions
+1 -1
View File
@@ -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>
@@ -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}
@@ -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
@@ -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>
@@ -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>
@@ -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 }
}
]);