Skip to content

Commit 8c0b034

Browse files
committed
add: add domain flow from studio to console > sites.
1 parent 0422871 commit 8c0b034

3 files changed

Lines changed: 261 additions & 7 deletions

File tree

src/lib/studio/domainsTable.svelte

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
import { Link } from '$lib/elements';
4+
import { Button } from '$lib/elements/forms';
5+
import { sdk, RuleType, DeploymentResourceType, RuleTrigger } from '$lib/stores/sdk';
6+
import { Query, type Models } from '@appwrite.io/console';
7+
import {
8+
IconDotsHorizontal,
9+
IconExternalLink,
10+
IconPlus,
11+
IconRefresh,
12+
IconTrash
13+
} from '@appwrite.io/pink-icons-svelte';
14+
import {
15+
ActionMenu,
16+
Badge,
17+
Icon,
18+
Layout,
19+
Popover,
20+
Table,
21+
Typography,
22+
Skeleton,
23+
Divider
24+
} from '@appwrite.io/pink-svelte';
25+
import { base, resolve } from '$app/paths';
26+
import { page } from '$app/state';
27+
import { Click, trackEvent } from '$lib/actions/analytics';
28+
import { regionalProtocol } from '$routes/(console)/project-[region]-[project]/store';
29+
import { goto } from '$app/navigation';
30+
import DeleteDomainModal from '$routes/(console)/project-[region]-[project]/sites/site-[site]/domains/deleteDomainModal.svelte';
31+
import RetryDomainModal from '$routes/(console)/project-[region]-[project]/sites/site-[site]/domains/retryDomainModal.svelte';
32+
33+
let {
34+
siteId,
35+
region,
36+
projectId
37+
}: {
38+
siteId: string;
39+
region: string;
40+
projectId: string;
41+
} = $props();
42+
43+
let loading = $state(true);
44+
let showRetry = $state(false);
45+
let showDelete = $state(false);
46+
47+
let selectedProxyRule: Models.ProxyRule = $state(null);
48+
let proxyRules = $state<Models.ProxyRuleList | null>(null);
49+
50+
async function loadDomains() {
51+
loading = true;
52+
try {
53+
proxyRules = await sdk.forProject(region, projectId).proxy.listRules({
54+
queries: [
55+
Query.equal('type', [RuleType.DEPLOYMENT, RuleType.REDIRECT]),
56+
Query.equal('deploymentResourceType', DeploymentResourceType.SITE),
57+
Query.equal('deploymentResourceId', siteId),
58+
Query.equal('trigger', RuleTrigger.MANUAL),
59+
Query.limit(100)
60+
]
61+
});
62+
} catch (error) {
63+
console.error('Failed to load domains:', error);
64+
} finally {
65+
loading = false;
66+
}
67+
}
68+
69+
let previousDeleteState = $state(false);
70+
let previousRetryState = $state(false);
71+
72+
onMount(loadDomains);
73+
74+
$effect(() => {
75+
const wasDeleteOpen = previousDeleteState && !showDelete;
76+
const wasRetryOpen = previousRetryState && !showRetry;
77+
78+
if (wasDeleteOpen || wasRetryOpen) {
79+
loadDomains();
80+
}
81+
82+
previousDeleteState = showDelete;
83+
previousDeleteState = showDelete;
84+
previousRetryState = showRetry;
85+
});
86+
</script>
87+
88+
<Table.Root columns={[{ id: 'domain' }, { id: 'actions', width: 40 }]} let:root>
89+
<svelte:fragment slot="header" let:root>
90+
<Table.Header.Cell column="domain" {root}>Domain</Table.Header.Cell>
91+
<Table.Header.Cell column="actions" {root} />
92+
</svelte:fragment>
93+
{#if loading}
94+
{#each Array(2) as _}
95+
<Table.Row.Base {root}>
96+
<Table.Cell column="domain" {root}>
97+
<Layout.Stack direction="row" gap="xs" alignItems="center">
98+
<Skeleton variant="line" width={200} height={20} />
99+
</Layout.Stack>
100+
</Table.Cell>
101+
<Table.Cell column="actions" {root}>
102+
<Layout.Stack direction="row" justifyContent="flex-end">
103+
<Skeleton variant="line" width={24} height={12} />
104+
</Layout.Stack>
105+
</Table.Cell>
106+
</Table.Row.Base>
107+
{/each}
108+
{:else if proxyRules && proxyRules.total > 0}
109+
{#each proxyRules.rules as rule}
110+
<Table.Row.Base {root}>
111+
<Table.Cell column="domain" {root}>
112+
<Layout.Stack direction="row" gap="xs" alignItems="center">
113+
<Link external variant="quiet" href={`${$regionalProtocol}${rule.domain}`}>
114+
<Layout.Stack direction="row" gap="xxs" alignItems="center">
115+
<Typography.Text truncate>
116+
{rule.domain}
117+
</Typography.Text>
118+
<Icon size="xs" icon={IconExternalLink} />
119+
</Layout.Stack>
120+
</Link>
121+
122+
{#if rule.status === 'verifying'}
123+
<Badge variant="secondary" content="Verifying" size="s" />
124+
{:else if rule.status !== 'verified'}
125+
<Badge
126+
size="s"
127+
type="warning"
128+
variant="secondary"
129+
content="Verification failed"
130+
/>
131+
{/if}
132+
</Layout.Stack>
133+
</Table.Cell>
134+
<Table.Cell column="actions" {root}>
135+
<Popover
136+
let:toggle
137+
padding="none"
138+
>
139+
<Button
140+
text
141+
icon
142+
on:click={(e) => {
143+
e.preventDefault();
144+
toggle(e);
145+
}}>
146+
<Icon icon={IconDotsHorizontal} size="s" />
147+
</Button>
148+
149+
<svelte:fragment slot="tooltip" let:toggle>
150+
{@render domainActions(rule, toggle)}
151+
</svelte:fragment>
152+
</Popover>
153+
</Table.Cell>
154+
</Table.Row.Base>
155+
{/each}
156+
{/if}
157+
</Table.Root>
158+
159+
<Layout.Stack style="width: min-content;">
160+
<Button
161+
compact
162+
on:onclick={async () => {
163+
await goto(
164+
resolve(
165+
'/(console)/project-[region]-[project]/sites/site-[site]/domains/add-domain?types=false',
166+
{
167+
region,
168+
project: projectId,
169+
site: siteId
170+
}
171+
)
172+
);
173+
}}>
174+
<Icon icon={IconPlus} size="s" />
175+
Add domain
176+
</Button>
177+
</Layout.Stack>
178+
179+
{#if showDelete}
180+
<DeleteDomainModal bind:show={showDelete} {selectedProxyRule} />
181+
{/if}
182+
183+
{#if showRetry}
184+
<RetryDomainModal bind:show={showRetry} {selectedProxyRule} />
185+
{/if}
186+
187+
{#snippet domainActions(rule: Models.ProxyRule, toggle: (e: Event) => void)}
188+
<ActionMenu.Root>
189+
<ActionMenu.Item.Link href={`${$regionalProtocol}${rule.domain}`} external>
190+
Open domain
191+
</ActionMenu.Item.Link>
192+
{#if rule.status !== 'verified' && rule.status !== 'verifying'}
193+
<ActionMenu.Item.Button
194+
leadingIcon={IconRefresh}
195+
on:click={() => {
196+
selectedProxyRule = rule;
197+
showRetry = true;
198+
toggle();
199+
}}>
200+
Retry
201+
</ActionMenu.Item.Button>
202+
203+
<div class="action-menu-divider">
204+
<Divider />
205+
</div>
206+
{/if}
207+
208+
<ActionMenu.Item.Button
209+
status="danger"
210+
leadingIcon={IconTrash}
211+
on:click={() => {
212+
selectedProxyRule = rule;
213+
showDelete = true;
214+
toggle();
215+
trackEvent(Click.DomainDeleteClick, {
216+
source: 'studio_manage_domains'
217+
});
218+
}}>
219+
Delete
220+
</ActionMenu.Item.Button>
221+
</ActionMenu.Root>
222+
{/snippet}
223+
224+
<style>
225+
.action-menu-divider {
226+
margin-inline: -1rem;
227+
padding-block-end: 0.25rem;
228+
padding-block-start: 0.25rem;
229+
}
230+
</style>

src/lib/studio/studio-widget.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ export async function initImagine(
276276
callbacks?: {
277277
onProjectNameChange: () => void;
278278
onAddDomain: () => void | Promise<void>;
279-
onManageDomains: () => void | Promise<void>;
279+
onManageDomains: (primaryDomain?: string) => void | Promise<void>;
280280
}
281281
) {
282282
try {

src/lib/studio/studio.svelte

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
import './shim.css';
77
import { onMount } from 'svelte';
88
import { resolve } from '$app/paths';
9+
import { Link } from '$lib/elements';
910
import { app } from '$lib/stores/app';
1011
import { Dependencies } from '$lib/constants';
1112
import { goto, invalidate } from '$app/navigation';
12-
import { Layout, Typography } from '@appwrite.io/pink-svelte';
13+
import { IconExternalLink } from '@appwrite.io/pink-icons-svelte';
14+
import { Button, Layout, Typography, Icon } from '@appwrite.io/pink-svelte';
1315
import { ensureStudioComponent, initImagine, getWebComponents } from './studio-widget';
16+
import DomainsTable from './domainsTable.svelte';
1417
import SideSheet from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/layout/sidesheet.svelte';
1518
1619
const {
@@ -21,7 +24,9 @@
2124
projectId: string;
2225
} = $props();
2326
27+
const siteId = `project-${projectId}`;
2428
let showManageDomainsSheet = $state(false);
29+
let primaryDomainForSite = $state(`imagine-${projectId}.stage.appwrite.network`);
2530
2631
onMount(() => {
2732
ensureStudioComponent();
@@ -37,12 +42,15 @@
3742
{
3843
region,
3944
project: projectId,
40-
site: `project-${projectId}`
45+
site: siteId
4146
}
4247
)
4348
);
4449
},
45-
onManageDomains: () => {
50+
onManageDomains: (primaryDomain) => {
51+
if (primaryDomain) {
52+
primaryDomainForSite = primaryDomain;
53+
}
4654
showManageDomainsSheet = true;
4755
}
4856
});
@@ -63,9 +71,25 @@
6371
<div aria-hidden="true" style:display="none"></div>
6472

6573
<SideSheet title="Domains" bind:show={showManageDomainsSheet}>
66-
<Layout.Stack gap="xxs">
67-
<Typography.Text color="--fgcolor-neutral-tertiary">Primary domain</Typography.Text>
74+
<Layout.Stack gap="xl">
75+
<Layout.Stack gap="xxs">
76+
<Typography.Text color="--fgcolor-neutral-tertiary">Active domain</Typography.Text>
77+
78+
<Typography.Text>
79+
<Link size="m" external variant="quiet" href={primaryDomainForSite}>
80+
<Layout.Stack
81+
direction="row"
82+
gap="xxs"
83+
alignItems="center"
84+
alignContent="center">
85+
{primaryDomainForSite}
86+
87+
<Icon size="s" icon={IconExternalLink} />
88+
</Layout.Stack>
89+
</Link>
90+
</Typography.Text>
91+
</Layout.Stack>
6892

69-
<Typography.Text color="--fgcolor-neutral-tertiary"></Typography.Text>
93+
<DomainsTable {siteId} {region} {projectId} />
7094
</Layout.Stack>
7195
</SideSheet>

0 commit comments

Comments
 (0)