fix: create webhook wizard

This commit is contained in:
Torsten Dittmann
2025-02-27 18:46:55 +04:00
parent 0302ded20b
commit 0b17733c4e
32 changed files with 522 additions and 851 deletions
+1
View File
@@ -13,6 +13,7 @@ Sentry.init({
export const handleError: HandleClientError = Sentry.handleErrorWithSentry(
async ({ error, message, status }) => {
console.error(error);
if (error instanceof AppwriteException) {
status = error.code === 0 ? undefined : error.code;
message = error.message;
-1
View File
@@ -7,7 +7,6 @@
export let single = false;
export let target: string = null;
export let href: string = null;
export let marginTop = false;
export let allowCreate = true;
export let description = 'Need a hand? Learn more in our documentation.';
export let src: string = null;
+1 -1
View File
@@ -12,7 +12,7 @@
export let noStyle = false;
export let isModal = false;
export let onSubmit: (e: SubmitEvent) => Promise<void> | void;
export let onSubmit: (e: SubmitEvent) => Promise<unknown> | unknown;
let classes = '';
export { classes as class };
@@ -59,11 +59,11 @@
EmailTemplateType,
EmailTemplateLocale
} from '@appwrite.io/console';
import { Accordion, Layout, Alert, Tag, Badge, Link } from '@appwrite.io/pink-svelte';
import { Accordion, Layout, Alert, Badge, Link, Typography } from '@appwrite.io/pink-svelte';
export let data;
let emailOpen = 'verification';
let emailOpen = null;
$: emailVerificationOpen = emailOpen === 'verification';
$: emailMagicSessionOpen = emailOpen === 'magicSession';
$: emailOtpSessionOpen = emailOpen === 'otpSession';
@@ -72,27 +72,13 @@
$: email2FAVerificationOpen = emailOpen === 'mfaChallenge';
$: emailSessionAlertOpen = emailOpen === 'sessionAlert';
// let smsOpen = 'verification';
// $: smsVerificationOpen = smsOpen === 'verification';
// $: smsLoginOpen = smsOpen === 'login';
// $: smsInvitationOpen = smsOpen === 'invitation';
onMount(async () => {
openEmail('verification');
// openSms('verification');
});
openEmail('verification');
async function openEmail(type: string) {
type === emailOpen ? (emailOpen = null) : (emailOpen = type);
$emailTemplate = await loadEmailTemplate(data.project.$id, type, 'en');
$baseEmailTemplate = { ...$emailTemplate };
}
// async function openSms(type: string) {
// type === smsOpen ? (smsOpen = null) : (smsOpen = type);
// $smsTemplate = await loadSmsTemplate(projectId, type, 'en');
// $baseSmsTemplate = { ...$smsTemplate };
// }
</script>
<Container>
@@ -113,8 +99,9 @@
{/if}
<CardGrid>
<svelte:fragment slot="title"
>Email templates <Badge variant="secondary" content="Experimental" /></svelte:fragment>
<svelte:fragment slot="title">
Email templates <Badge variant="secondary" content="Experimental" />
</svelte:fragment>
Use templates to send and process account management emails.
<Link.Anchor href="https://appwrite.io/docs/advanced/platform/message-templates">
Learn more
@@ -124,16 +111,16 @@
<Accordion
title="Verification"
bind:open={emailVerificationOpen}
on:click={(e) => {
// preventing default and propagation to open the collapsible correctly
e.preventDefault();
e.stopImmediatePropagation();
openEmail('verification');
}}>
Send a verification email to users that sign in with their email and password.
<EmailVerificationTemplate />
on:click={() => openEmail('verification')}>
<Layout.Stack>
<Typography.Text>
Send a verification email to users that sign in with their email and
password.
</Typography.Text>
<EmailVerificationTemplate />
</Layout.Stack>
</Accordion>
<Accordion
<!-- <Accordion
title="Magic URL"
bind:open={emailMagicSessionOpen}
on:click={(e) => {
@@ -192,7 +179,7 @@
}}>
Send an email to users when a new session is created.
<EmailSessionAlertTemplate />
</Accordion>
</Accordion> -->
</Layout.Stack>
</svelte:fragment>
<svelte:fragment slot="actions">
@@ -201,55 +188,6 @@
</Button>
</svelte:fragment>
</CardGrid>
<!-- <CardGrid>
<Heading size="7" tag="h3">SMS templates</Heading>
<p class="text">
Use templates to send and process account management mobile messages. <a
href="https://appwrite.io/docs/advanced/platform/message-templates"
class="link">
Learn more about SMS templates</a
</p>
<svelte:fragment slot="aside">
<Collapsible>
<CollapsibleItem
bind:open={smsVerificationOpen}
on:click={(e) => {
e.preventDefault();
openSms('verification');
}}>
<svelte:fragment slot="title">Verification</svelte:fragment>
<p class="text">
Send a verification SMS to users that sign in with their phone
</p>
<SmsVerificationTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={smsLoginOpen}
on:click={(e) => {
e.preventDefault();
openSms('login');
}}>
<svelte:fragment slot="title">Login</svelte:fragment>
<p class="text">
Send a one-time passcode to users' mobile phones to allow them to sign in.
</p>
<SmsLoginTemplate />
</CollapsibleItem>
<CollapsibleItem
bind:open={smsInvitationOpen}
on:click={(e) => {
e.preventDefault();
openSms('invitation');
}}>
<svelte:fragment slot="title">Invitation</svelte:fragment>
<p class="text">Send an invitation SMS to become a member of your project.</p>
<SmsLoginTemplate />
</CollapsibleItem>
</Collapsible>
</svelte:fragment>
</CardGrid>-->
{#if isCloud && $currentPlan.emailBranding}
<EmailSignature />
{/if}
@@ -35,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
@@ -35,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -37,7 +37,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
@@ -36,7 +36,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
@@ -35,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
@@ -1,12 +1,5 @@
<script lang="ts">
import {
Button,
Form,
FormList,
InputEmail,
InputText,
InputTextarea
} from '$lib/elements/forms';
import { Button, Form, InputEmail, InputText, InputTextarea } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { project } from '../../store';
@@ -14,9 +7,8 @@
import { baseEmailTemplate, emailTemplate } from './store';
import deepEqual from 'deep-equal';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Box } from '$lib/components';
import type { EmailTemplateLocale, EmailTemplateType } from '@appwrite.io/console';
import { Layout } from '@appwrite.io/pink-svelte';
import { Card, Layout } from '@appwrite.io/pink-svelte';
export let loading = false;
let openResetModal = false;
@@ -87,11 +79,10 @@
}
$: isButtonDisabled = deepEqual($emailTemplate, $baseEmailTemplate);
$: isSmtpEnabled = $project?.smtpEnabled;
</script>
<Box class="u-position-relative">
<Card.Base variant="secondary" padding="s">
{#if loading}
<div
class="u-position-absolute u-width-full-line u-flex u-flex-vertical u-main-center u-cross-center u-gap-16 u-margin-block-start-32"
@@ -107,16 +98,14 @@
id="senderName"
label="Sender name"
bind:value={$emailTemplate.senderName}
tooltip="Set up an SMTP server to edit the sender name"
placeholder="Enter sender name"
readonly={!isSmtpEnabled} />
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.senderEmail}
id="senderEmail"
label="Sender email"
tooltip="Set up an SMTP server to edit the sender email"
placeholder="Enter sender email"
readonly={!isSmtpEnabled} />
disabled={!isSmtpEnabled} />
<InputEmail
bind:value={$emailTemplate.replyTo}
id="replyTo"
@@ -154,6 +143,6 @@
</div>
</Form>
</div>
</Box>
</Card.Base>
<ResetEmail bind:show={openResetModal} />
@@ -7,6 +7,7 @@
import { Id } from '$lib/components';
import { addNotification } from '$lib/stores/notifications';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Layout } from '@appwrite.io/pink-svelte';
const projectId = $page.params.project;
@@ -35,12 +36,12 @@
}
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<Layout.Stack>
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<EmailTemplate bind:loading>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
<Id value={'{{project}}'}>{'{{project}}'}</Id>
<Id value={'{{redirect}}'}>{'{{redirect}}'}</Id>
</EmailTemplate>
</div>
</Layout.Stack>
@@ -1,6 +1,5 @@
<script lang="ts">
import { Box } from '$lib/components';
import { Input } from '@appwrite.io/pink-svelte';
import { Card, Input } from '@appwrite.io/pink-svelte';
import { localeCodes } from './store';
export let value = 'en';
@@ -11,21 +10,14 @@
}));
</script>
<Box radius="small" class="u-flex u-gap-16 u-cross-center">
<div class="u-un-break-text is-not-mobile">
<span class="icon-translate" />
<span class="text">Template language</span>
</div>
<Input.Combobox
on:select
<Card.Base variant="secondary" padding="s">
<Input.ComboBox
label="Template language"
on:change
id="language"
placeholder="Select a language"
name="locale"
bind:value
required
fullWidth
stretch={false}
showLabel={false}
interactiveOutput
{options} />
</Box>
</Card.Base>
@@ -35,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<SmsTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -36,7 +36,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<SmsTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -35,7 +35,7 @@
</script>
<div class="boxes-wrapper u-margin-block-start-16">
<LocaleOptions on:select={onLocaleChange} bind:value={locale} />
<LocaleOptions on:change={onLocaleChange} bind:value={locale} />
<SmsTemplate bind:loading>
<Id value={'{{team}}'}>{'{{team}}'}</Id>
<Id value={'{{user}}'}>{'{{user}}'}</Id>
@@ -9,7 +9,7 @@
import { preferences } from '$lib/stores/preferences';
import { canWriteCollections, canWriteDocuments } from '$lib/stores/roles';
import { wizard } from '$lib/stores/wizard';
import { Icon } from '@appwrite.io/pink-svelte';
import { Card, Icon, Layout, Empty as PinkEmpty } from '@appwrite.io/pink-svelte';
import type { PageData } from './$types';
import CreateAttributeDropdown from './attributes/createAttributeDropdown.svelte';
import type { Option } from './attributes/store';
@@ -22,7 +22,6 @@
export let data: PageData;
let showCreateAttribute = false;
let showCreateDropdown = false;
let selectedAttribute: Option['name'] = null;
$: selected = preferences.getCustomCollectionColumns($page.params.collection);
@@ -50,9 +49,13 @@
{#key $page.params.collection}
<Container>
<div class="heading-grid u-main-justify-between u-cross-center">
<Heading tag="h2" size="5">Documents</Heading>
<div class="u-flex u-main-end is-only-mobile">
<Layout.Stack direction="row" justifyContent="space-between">
<Filters
query={data.query}
{columns}
disabled={!(hasAttributes && hasValidAttributes)} />
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<ViewSelector view={data.view} {columns} hideView allowNoColumns />
<Button
disabled={!(hasAttributes && hasValidAttributes)}
on:click={openWizard}
@@ -60,32 +63,8 @@
<Icon icon={IconPlus} slot="start" size="s" />
Create document
</Button>
</div>
<Filters
query={data.query}
{columns}
disabled={!(hasAttributes && hasValidAttributes)} />
<div class="u-flex u-main-end u-gap-16">
<ViewSelector
view={data.view}
{columns}
isCustomCollection
hideView
allowNoColumns
showColsTextMobile />
<div class="is-not-mobile">
<Button
disabled={!(hasAttributes && hasValidAttributes)}
on:click={openWizard}
event="create_document">
<Icon icon={IconPlus} slot="start" size="s" />
Create document
</Button>
</div>
</div>
</div>
</Layout.Stack>
</Layout.Stack>
{#if hasAttributes && hasValidAttributes}
{#if data.documents.total}
@@ -124,57 +103,36 @@
on:click={openWizard} />
{/if}
{:else}
<Empty
allowCreate={$canWriteCollections}
single
target="attribute"
on:click={() => (showCreateDropdown = true)}>
<div class="u-text-center">
<Heading size="7" tag="h2">Create an attribute to get started.</Heading>
<p class="body-text-2 u-bold u-margin-block-start-4">
Need a hand? Learn more in our documentation.
</p>
</div>
<div class="u-flex u-gap-16 u-main-center">
<Button
external
href="https://appwrite.io/docs/products/databases/collections#attributes"
text
event="empty_documentation"
ariaLabel={`create {target}`}>Documentation</Button>
{#if $canWriteCollections}
<CreateAttributeDropdown
bind:showCreateDropdown
bind:showCreate={showCreateAttribute}
bind:selectedOption={selectedAttribute}>
<Button
secondary
event="create_attribute"
on:click={() => {
showCreateDropdown = !showCreateDropdown;
}}>
Create attribute
</Button>
</CreateAttributeDropdown>
{/if}
</div>
</Empty>
<Card.Base padding="none">
<PinkEmpty
title="Create an attribute to get started."
description="Need a hand? Learn more in our documentation.">
<slot name="actions" slot="actions">
<Button
external
href="https://appwrite.io/docs/products/databases/collections#attributes"
text
event="empty_documentation"
size="s">Documentation</Button>
{#if $canWriteCollections}
<CreateAttributeDropdown
bind:selectedOption={selectedAttribute}
bind:showCreate={showCreateAttribute}
let:toggle>
<Button secondary event="create_attribute" on:click={toggle}>
Create attribute
</Button>
</CreateAttributeDropdown>
{/if}
</slot>
</PinkEmpty>
</Card.Base>
{/if}
</Container>
{/key}
<CreateAttribute bind:showCreate={showCreateAttribute} bind:selectedOption={selectedAttribute} />
<style lang="scss">
.heading-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
@media (min-width: 768px) {
:global(h2) {
grid-column: span 2;
}
}
}
</style>
{#if showCreateAttribute}
<CreateAttribute
bind:showCreate={showCreateAttribute}
bind:selectedOption={selectedAttribute} />
{/if}
@@ -36,6 +36,7 @@
<script lang="ts">
import { InputChoice, InputSelect } from '$lib/elements/forms';
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
export let editing = false;
export let data: Partial<Models.AttributeBoolean> = {
@@ -45,6 +46,7 @@
};
import { createConservative } from '$lib/helpers/stores';
import { Selector } from '@appwrite.io/pink-svelte';
let savedDefault = data.default;
@@ -81,10 +83,18 @@
{ label: 'False', value: false }
]}
bind:value={data.default} />
<InputChoice id="required" label="Required" bind:value={data.required} disabled={data.array}>
Indicate whether this is a required attribute
</InputChoice>
<InputChoice id="array" label="Array" bind:value={data.array} disabled={data.required || editing}>
Indicate whether this attribute should act as an array, with the default value set as an empty
array.
</InputChoice>
<Selector.Checkbox
size="s"
id="required"
label="Required"
bind:checked={data.required}
disabled={data.array}
description="Indicate whether this attribute is required" />
<Selector.Checkbox
size="s"
id="array"
label="Array"
bind:checked={data.array}
disabled={data.required || editing}
description="Indicate whether this attribute should act as an array, with the default value set as an empty
array." />
@@ -66,53 +66,29 @@
}
</script>
<Modal
{error}
size="big"
bind:show={showCreate}
onSubmit={submit}
icon={$option?.icon}
iconNotMobile={selectedOption === 'Relationship'}>
<svelte:fragment slot="title">
{#if selectedOption === 'Relationship'}
<span class="u-flex u-gap-16 u-cross-center">
{selectedOption}
<div class="tag eyebrow-heading-3">
<span class="text u-x-small">Experimental</span>
</div>
</span>
{:else}
{selectedOption}
{/if}
</svelte:fragment>
<FormList>
{#if selectedOption !== 'Relationship'}
<div>
<InputText
id="key"
label="Attribute Key"
placeholder="Enter Key"
bind:value={key}
autofocus
required />
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
<span
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
aria-hidden="true" />
<span class="text u-line-height-1-5">
Allowed characters: alphanumeric, hyphen, non-leading underscore, period.
</span>
</div>
<Modal {error} bind:show={showCreate} onSubmit={submit} title={selectedOption}>
{#if selectedOption !== 'Relationship'}
<div>
<InputText
id="key"
label="Attribute Key"
placeholder="Enter Key"
bind:value={key}
autofocus
required />
<div class="u-flex u-gap-4 u-margin-block-start-8 u-small">
<span
class="icon-info u-cross-center u-margin-block-start-2 u-line-height-1 u-icon-small"
aria-hidden="true" />
<span class="text u-line-height-1-5">
Allowed characters: alphanumeric, hyphen, non-leading underscore, period.
</span>
</div>
{/if}
{#if selectedOption}
<svelte:component
this={$option.component}
bind:data
on:close={() => ($option = null)} />
{/if}
</FormList>
</div>
{/if}
{#if selectedOption}
<svelte:component this={$option.component} bind:data on:close={() => ($option = null)} />
{/if}
<svelte:fragment slot="footer">
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
<Button submit disabled={!selectedOption}>Create</Button>
@@ -1,8 +1,9 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { registerCommands, updateCommandGroupRanks } from '$lib/commandCenter';
import { canWriteProjects } from '$lib/stores/roles';
import { openWebhooksWizard } from './webhooks/+page.svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
$: $registerCommands([
@@ -11,7 +12,7 @@
icon: IconPlus,
keys: $page.url.pathname.includes('webhooks') ? ['c'] : ['c', 'w'],
callback: () => {
openWebhooksWizard();
goto(`${base}/project-${$page.params.project}/settings/webhooks/create`);
},
group: 'webhooks',
disabled: !$canWriteProjects
@@ -12,8 +12,8 @@
import UpdateInstallations from './updateInstallations.svelte';
import UpdateVariables from '../updateVariables.svelte';
import DeleteProject from './deleteProject.svelte';
import { CardGrid, Heading } from '$lib/components';
import { Button, FormList, InputSelect } from '$lib/elements/forms';
import { CardGrid } from '$lib/components';
import { Button, InputSelect } from '$lib/elements/forms';
import { Submit, trackEvent } from '$lib/actions/analytics';
import Transfer from './transferProject.svelte';
import { canWriteProjects } from '$lib/stores/roles';
@@ -3,7 +3,6 @@
import { page } from '$app/stores';
import { Wizard } from '$lib/layout';
import {
Alert,
Card,
Fieldset,
Icon,
@@ -15,14 +14,12 @@
import Button from '$lib/elements/forms/button.svelte';
import Form from '$lib/elements/forms/form.svelte';
import { sdk } from '$lib/stores/sdk';
import { onboarding } from '$routes/(console)/project-[project]/store';
import { goto, invalidate } from '$app/navigation';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import { writable } from 'svelte/store';
import Retry from '$lib/pages/domains/wizard/retry.svelte';
import Box from '$lib/components/box.svelte';
import { IconClock } from '@appwrite.io/pink-icons-svelte';
let retrying = false;
@@ -67,17 +64,16 @@
column>
<Form {onSubmit} bind:isSubmitting>
<Layout.Stack gap="xxl">
<Card.Base padding="s"
><Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Layout.Stack gap="xxs" direction="row">
<Typography.Text variant="m-500">{data.domain.domain}</Typography.Text>
</Layout.Stack>
<Card.Base padding="s">
<Layout.Stack direction="row" justifyContent="space-between" alignItems="center">
<Typography.Text variant="m-500">{data.domain.domain}</Typography.Text>
<Button
secondary
size="s"
href={`${base}/project-${$page.params.project}/settings/domains/create?domain=${data.domain.domain}&$id=${data.domain.$id}`}
>Change</Button>
</Layout.Stack></Card.Base>
</Layout.Stack>
</Card.Base>
<Fieldset legend="DNS">
<Layout.Stack gap="l" alignItems="flex-end">
<Layout.Stack>
@@ -1,55 +1,27 @@
<script lang="ts" context="module">
export function openWebhooksWizard() {
if (!get(canWriteWebhooks)) {
return;
}
wizard.start(Create);
}
</script>
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Empty, Id } from '$lib/components';
import {
TableBody,
TableRowLink,
TableCellHead,
TableCell,
TableCellText,
TableHeader,
TableScroll
} from '$lib/elements/table';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import { wizard } from '$lib/stores/wizard';
import type { PageData } from './$types';
import { columns } from './store';
import { updateCommandGroupRanks } from '$lib/commandCenter';
import { type Models } from '@appwrite.io/console';
import Create from './createWebhook.svelte';
import FailedModal from './failedModal.svelte';
import MessageStatusPill from './messageStatusPill.svelte';
import { canWriteWebhooks } from '$lib/stores/roles';
import { get } from 'svelte/store';
import { Badge, Icon, Layout, Link, Status, Table } from '@appwrite.io/pink-svelte';
import { Icon, Layout, Link, Status, Table } from '@appwrite.io/pink-svelte';
import ViewSelector from '$lib/components/viewSelector.svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { View } from '$lib/helpers/load';
import { goto } from '$app/navigation';
export let data: PageData;
let showFailed = false;
let selectedWebhook: Models.Webhook;
function openWizard() {
if (!$canWriteWebhooks) {
return;
}
wizard.start(Create);
}
const projectId = $page.params.project;
$: $updateCommandGroupRanks({ webhooks: 20, domains: 10 });
@@ -63,7 +35,9 @@
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<ViewSelector {columns} view={View.Table} hideView />
{#if $canWriteWebhooks}
<Button on:click={openWizard} event="create_webhook">
<Button
href={`${base}/project-${projectId}/settings/webhooks/create`}
event="create_webhook">
<Icon icon={IconPlus} slot="start" size="s" />
Create webhook
</Button>
@@ -135,7 +109,7 @@
single
href="https://appwrite.io/docs/advanced/platform/webhooks"
target="webhook"
on:click={openWizard} />
on:click={() => goto(`${base}/projects-${projectId}/settings/webhooks/create`)} />
{/if}
</Container>
@@ -0,0 +1,70 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Wizard } from '$lib/layout';
import { Card, Layout, Typography } from '@appwrite.io/pink-svelte';
import Form from '$lib/elements/forms/form.svelte';
import { goto } from '$app/navigation';
import { sdk } from '$lib/stores/sdk';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { addNotification } from '$lib/stores/notifications';
import Step1 from './step1.svelte';
import Step2 from './step2.svelte';
import Step3 from './step3.svelte';
import Button from '$lib/elements/forms/button.svelte';
let showExitModal = false;
let name = '',
events = [],
url = '',
security = true,
httpUser = '',
httpPass = '';
export let data;
async function create() {
try {
const webhook = await sdk.forConsole.projects.createWebhook(
data.project.$id,
name,
events,
url,
security,
true,
httpUser || undefined,
httpPass || undefined
);
addNotification({
message: 'Webhook has been created',
type: 'success'
});
trackEvent(Submit.WebhookCreate, {
events: events
});
goto(`${base}/project-${$page.params.project}/settings/webhooks/${webhook.$id}`);
} catch (error) {
trackError(error.message, Submit.DomainCreate);
throw new Error(error.message);
}
}
</script>
<Form onSubmit={create}>
<Wizard
title="Create webhook"
href={`${base}/project-${$page.params.project}/settings/webhooks`}
bind:showExitModal
column
confirmExit>
<Layout.Stack gap="xxl">
<Step1 bind:name bind:url />
<Step2 bind:events />
<Step3 bind:httpUser bind:httpPass bind:security />
</Layout.Stack>
<svelte:fragment slot="footer">
<Button submit>Create webhook</Button>
</svelte:fragment>
</Wizard>
</Form>
@@ -1,27 +1,19 @@
<script lang="ts">
import { InputText, InputURL, FormList } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import { InputText, InputURL } from '$lib/elements/forms';
import { Fieldset, Layout } from '@appwrite.io/pink-svelte';
export let name: string;
export let url: string;
</script>
<WizardStep>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Use webhooks to build integrations that subscribe to specific events on Appwrite. Appwrite
will send an HTTP POST payload to the webhook's configured URL when one of the specified
events is triggered.
</svelte:fragment>
<FormList>
<InputText
label="Name"
id="name"
placeholder="Enter name"
bind:value={$createWebhook.name}
required />
<Fieldset legend="Configuration">
<Layout.Stack gap="s">
<InputText label="Name" id="name" placeholder="Enter name" bind:value={name} required />
<InputURL
label="POST URL"
id="url"
placeholder="https://example.com/callback"
bind:value={$createWebhook.url}
bind:value={url}
required />
</FormList>
</WizardStep>
</Layout.Stack>
</Fieldset>
@@ -1,63 +1,59 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import { Empty } from '$lib/components';
import { EventModal } from '$lib/components';
import { Icon, Typography, Link, Table, Layout } from '@appwrite.io/pink-svelte';
import { Icon, Typography, Link, Table, Layout, Fieldset } from '@appwrite.io/pink-svelte';
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
let showCreate = false;
export let events: string[];
let eventSet = new Set<string>();
const eventSet = new Set<string>();
function handleCreated(event: CustomEvent) {
eventSet.add(event.detail);
$createWebhook.events = Array.from(eventSet);
events = Array.from(eventSet);
}
</script>
<WizardStep>
<svelte:fragment slot="title">Webhook events</svelte:fragment>
<svelte:fragment slot="subtitle">
Set the events that will trigger your function. Maximum 100 events allowed.
</svelte:fragment>
{#if $createWebhook?.events?.length}
{#each $createWebhook.events as event}
<Fieldset legend="Events">
<Layout.Stack gap="s">
{#if events.length}
<Table.Root>
<Table.Row>
<Table.Cell>
<Layout.Stack
direction="row"
justifyContent="space-between"
alignItems="center">
{event}
<Button
extraCompact
ariaLabel="delete event"
on:click={() => {
eventSet.delete(event);
$createWebhook.events = Array.from(eventSet);
}}>
<Icon icon={IconX} size="s" />
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
{#each events as event}
<Table.Row>
<Table.Cell>
<Layout.Stack
direction="row"
justifyContent="space-between"
alignItems="center">
{event}
<Button
extraCompact
ariaLabel="delete event"
on:click={() => {
eventSet.delete(event);
events = Array.from(eventSet);
}}>
<Icon icon={IconX} size="s" />
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
{/each}
</Table.Root>
{/each}
<div>
<Button secondary on:click={() => (showCreate = !showCreate)}>
<Icon icon={IconPlus} slot="start" size="s" />
Add event
</Button>
</div>
{:else}
<Empty on:click={() => (showCreate = !showCreate)}>Add an event</Empty>
{/if}
</WizardStep>
<div>
<Button secondary on:click={() => (showCreate = !showCreate)}>
<Icon icon={IconPlus} slot="start" size="s" />
Add event
</Button>
</div>
{:else}
<Empty on:click={() => (showCreate = !showCreate)}>Add an event</Empty>
{/if}
</Layout.Stack>
</Fieldset>
{#if showCreate}
<EventModal bind:show={showCreate} on:created={handleCreated}>
@@ -1,36 +1,46 @@
<script lang="ts">
import { InputText, InputPassword, InputChoice } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import Button from '$lib/elements/forms/button.svelte';
import { Alert, Fieldset, Layout, Link, Typography } from '@appwrite.io/pink-svelte';
export let httpUser: string;
export let httpPass: string;
export let security: boolean;
</script>
<WizardStep>
<svelte:fragment slot="title">Security</svelte:fragment>
<svelte:fragment slot="subtitle">
Set an optional basic HTTP authentication username and password to protect your endpoint
from unauthorized access.
</svelte:fragment>
<div>
<h2 class="heading-level-7">HTTP Authentication</h2>
<p class="text">Use to secure your endpoint from untrusted sources.</p>
</div>
<InputText label="User" id="user" placeholder="Enter username" bind:value={httpUser} />
<InputPassword
label="Password"
id="password"
placeholder="Enter password"
minlength={0}
bind:value={httpPass} />
<InputChoice id="Security" label="Certificate verification (SSL/TLS)" bind:value={security}>
<span class="u-color-text-danger">Warning:</span> Untrusted or self-signed certificates may
not be secure.
<a
href="https://appwrite.io/docs/advanced/self-hosting/tls-certificates"
target="_blank"
rel="noopener noreferrer"
class="link">
Learn more</a
></InputChoice>
</WizardStep>
<Fieldset legend="Security">
<Layout.Stack gap="xl">
<Layout.Stack gap="s">
<InputChoice
type="switchbox"
id="Security"
label="Certificate verification (SSL/TLS)"
bind:value={security} />
{#if !security}
<Alert.Inline
status="warning"
title="Untrusted or self-signed certificates may not be secure.">
<Button
compact
href="https://appwrite.io/docs/advanced/self-hosting/tls-certificates"
external>
Learn more</Button>
</Alert.Inline>
{/if}
</Layout.Stack>
<Layout.Stack gap="m">
<div>
<Typography.Text variant="m-600">HTTP authentication</Typography.Text>
<Typography.Text
>Use to secure your endpoint from untrusted sources.</Typography.Text>
</div>
<InputText label="User" id="user" placeholder="Enter username" bind:value={httpUser} />
<InputPassword
label="Password"
id="password"
placeholder="Enter password"
minlength={0}
bind:value={httpPass} />
</Layout.Stack>
</Layout.Stack>
</Fieldset>
@@ -1,77 +0,0 @@
<script lang="ts">
import { WizardWithSteps } from '$lib/layout';
import { onDestroy } from 'svelte';
import { addNotification } from '$lib/stores/notifications';
import { wizard } from '$lib/stores/wizard';
import { createWebhook } from './wizard/store';
import { sdk } from '$lib/stores/sdk';
import { page } from '$app/stores';
import Step1 from './wizard/step1.svelte';
import Step2 from './wizard/step2.svelte';
import Step3 from './wizard/step3.svelte';
import type { WizardStepsType } from '$lib/layout/wizardWithSteps.svelte';
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
const projectId = $page.params.project;
async function create() {
try {
await sdk.forConsole.projects.createWebhook(
projectId,
$createWebhook.name,
$createWebhook.events,
$createWebhook.url,
$createWebhook.security,
true,
$createWebhook.httpUser || undefined,
$createWebhook.httpPass || undefined
);
await invalidate(Dependencies.WEBHOOKS);
addNotification({
message: 'Webhook has been created',
type: 'success'
});
wizard.hide();
trackEvent(Submit.WebhookCreate, {
events: $createWebhook.events
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.WebhookCreate);
}
}
onDestroy(() => {
$createWebhook = {
name: null,
url: null,
events: [],
security: false,
httpUser: null,
httpPass: null
};
});
const stepsComponents: WizardStepsType = new Map();
stepsComponents.set(1, {
label: 'Configure',
component: Step1
});
stepsComponents.set(2, {
label: 'Events',
component: Step2
});
stepsComponents.set(3, {
label: 'Security',
component: Step3,
optional: true
});
</script>
<WizardWithSteps confirmExit title="Create Webhook" steps={stepsComponents} on:finish={create}>
<svelte:fragment slot="exit">Are you sure you want to exit from this process?</svelte:fragment>
</WizardWithSteps>
@@ -1,28 +0,0 @@
<script lang="ts">
import { InputText, InputURL, FormList } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import { createWebhook } from './store';
</script>
<WizardStep>
<svelte:fragment slot="title">Configuration</svelte:fragment>
<svelte:fragment slot="subtitle">
Use webhooks to build integrations that subscribe to specific events on Appwrite. Appwrite
will send an HTTP POST payload to the webhook's configured URL when one of the specified
events is triggered.
</svelte:fragment>
<FormList>
<InputText
label="Name"
id="name"
placeholder="Enter name"
bind:value={$createWebhook.name}
required />
<InputURL
label="POST URL"
id="url"
placeholder="https://example.com/callback"
bind:value={$createWebhook.url}
required />
</FormList>
</WizardStep>
@@ -1,71 +0,0 @@
<script lang="ts">
import { WizardStep } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import { Empty } from '$lib/components';
import { createWebhook } from './store';
import { EventModal } from '$lib/components';
import { Icon, Typography, Link, Table, Layout } from '@appwrite.io/pink-svelte';
import { IconPlus, IconX } from '@appwrite.io/pink-icons-svelte';
let showCreate = false;
let eventSet = new Set<string>();
function handleCreated(event: CustomEvent) {
eventSet.add(event.detail);
$createWebhook.events = Array.from(eventSet);
}
</script>
<WizardStep>
<svelte:fragment slot="title">Webhook events</svelte:fragment>
<svelte:fragment slot="subtitle">
Set the events that will trigger your function. Maximum 100 events allowed.
</svelte:fragment>
{#if $createWebhook?.events?.length}
{#each $createWebhook.events as event}
<Table.Root>
<Table.Row>
<Table.Cell>
<Layout.Stack
direction="row"
justifyContent="space-between"
alignItems="center">
{event}
<Button
extraCompact
ariaLabel="delete event"
on:click={() => {
eventSet.delete(event);
$createWebhook.events = Array.from(eventSet);
}}>
<Icon icon={IconX} size="s" />
</Button>
</Layout.Stack>
</Table.Cell>
</Table.Row>
</Table.Root>
{/each}
<div>
<Button secondary on:click={() => (showCreate = !showCreate)}>
<Icon icon={IconPlus} slot="start" size="s" />
Add event
</Button>
</div>
{:else}
<Empty on:click={() => (showCreate = !showCreate)}>Add an event</Empty>
{/if}
</WizardStep>
{#if showCreate}
<EventModal bind:show={showCreate} on:created={handleCreated}>
<Typography.Text>
Select events in your Appwrite project that will trigger your webhook. <Link.Anchor
href="https://appwrite.io/docs/advanced/platform/events"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</Link.Anchor>
</Typography.Text>
</EventModal>
{/if}
@@ -1,45 +0,0 @@
<script lang="ts">
import { FormList, InputText, InputPassword, InputChoice } from '$lib/elements/forms';
import { WizardStep } from '$lib/layout';
import { createWebhook } from './store';
</script>
<WizardStep>
<svelte:fragment slot="title">Security</svelte:fragment>
<svelte:fragment slot="subtitle">
Set an optional basic HTTP authentication username and password to protect your endpoint
from unauthorized access.
</svelte:fragment>
<FormList>
<div>
<h2 class="heading-level-7">HTTP Authentication</h2>
<p class="text">Use to secure your endpoint from untrusted sources.</p>
</div>
<InputText
label="User"
id="user"
placeholder="Enter username"
bind:value={$createWebhook.httpUser} />
<InputPassword
label="Password"
id="password"
showPasswordButton
placeholder="Enter password"
minlength={0}
bind:value={$createWebhook.httpPass} />
<InputChoice
id="Security"
label="Certificate verification (SSL/TLS)"
bind:value={$createWebhook.security}>
<span class="u-color-text-danger">Warning:</span> Untrusted or self-signed certificates
may not be secure.
<a
href="https://appwrite.io/docs/advanced/self-hosting/tls-certificates"
target="_blank"
rel="noopener noreferrer"
class="link">
Learn more</a
></InputChoice>
</FormList>
</WizardStep>
@@ -1,11 +0,0 @@
import type { Models } from '@appwrite.io/console';
import { writable } from 'svelte/store';
export const createWebhook = writable<Partial<Models.Webhook>>({
name: null,
url: null,
events: [],
security: false,
httpUser: null,
httpPass: null
});