Merge pull request #2976 from appwrite/fix-project-services-region-and-sdk-list

fix(console): regional Projects API for service toggles + full ApiSer…
This commit is contained in:
Harsh Mahajan
2026-04-16 19:12:05 +05:30
committed by GitHub
16 changed files with 566 additions and 182 deletions
+3 -2
View File
@@ -1,11 +1,12 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "@appwrite/console",
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@67539a6",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f063676",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
"@appwrite.io/pink-legacy": "^1.0.3",
@@ -112,7 +113,7 @@
"@analytics/type-utils": ["@analytics/type-utils@0.6.4", "", {}, "sha512-Ou1gQxFakOWLcPnbFVsrPb8g1wLLUZYYJXDPjHkG07+5mustGs5yqACx42UAu4A6NszNN6Z5gGxhyH45zPWRxw=="],
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@67539a6", { "dependencies": { "json-bigint": "1.0.0" } }],
"@appwrite.io/console": ["@appwrite.io/console@https://pkg.vc/-/@appwrite/@appwrite.io/console@f063676", { "dependencies": { "json-bigint": "1.0.0" } }],
"@appwrite.io/pink-icons": ["@appwrite.io/pink-icons@0.25.0", "", {}, "sha512-0O3i2oEuh5mWvjO80i+X6rbzrWLJ1m5wmv2/M3a1p2PyBJsFxN8xQMTEmTn3Wl/D26SsM7SpzbdW6gmfgoVU9Q=="],
+1 -1
View File
@@ -20,7 +20,7 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@67539a6",
"@appwrite.io/console": "https://pkg.vc/-/@appwrite/@appwrite.io/console@f063676",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-icons-svelte@bfe7ce3",
"@appwrite.io/pink-legacy": "^1.0.3",
+44
View File
@@ -0,0 +1,44 @@
import { writable } from 'svelte/store';
import { ProtocolId, type Models } from '@appwrite.io/console';
export type Protocol = {
label: string;
method: ProtocolId;
value: boolean | null;
};
function projectProtocolRows(project: Models.Project | null): Protocol[] {
return [
{
label: 'REST',
method: ProtocolId.Rest,
value: project?.protocolStatusForRest ?? null
},
{
label: 'GraphQL',
method: ProtocolId.Graphql,
value: project?.protocolStatusForGraphql ?? null
},
{
label: 'WebSocket',
method: ProtocolId.Websocket,
value: project?.protocolStatusForWebsocket ?? null
}
];
}
function createProtocols() {
const { subscribe, set } = writable({
list: projectProtocolRows(null)
});
return {
subscribe,
set,
load: (project: Models.Project) => {
set({ list: projectProtocolRows(project) });
}
};
}
export const protocols = createProtocols();
+98 -102
View File
@@ -1,120 +1,116 @@
import { writable } from 'svelte/store';
import { ApiService, type Models } from '@appwrite.io/console';
import { ServiceId, type Models } from '@appwrite.io/console';
export type Service = {
label: string;
method: ApiService;
method: ServiceId;
value: boolean | null;
};
function projectServiceRows(project: Models.Project | null): Service[] {
const rows: Service[] = [
{
label: 'Account',
method: ServiceId.Account,
value: project?.serviceStatusForAccount ?? null
},
{
label: 'Avatars',
method: ServiceId.Avatars,
value: project?.serviceStatusForAvatars ?? null
},
{
label: 'Databases',
method: ServiceId.Databases,
value: project?.serviceStatusForDatabases ?? null
},
{
label: 'Functions',
method: ServiceId.Functions,
value: project?.serviceStatusForFunctions ?? null
},
{
label: 'GraphQL',
method: ServiceId.Graphql,
value: project?.serviceStatusForGraphql ?? null
},
{
label: 'Health',
method: ServiceId.Health,
value: project?.serviceStatusForHealth ?? null
},
{
label: 'Locale',
method: ServiceId.Locale,
value: project?.serviceStatusForLocale ?? null
},
{
label: 'Messaging',
method: ServiceId.Messaging,
value: project?.serviceStatusForMessaging ?? null
},
{
label: 'Migrations',
method: ServiceId.Migrations,
value: project?.serviceStatusForMigrations ?? null
},
{
label: 'Project',
method: ServiceId.Project,
value: project?.serviceStatusForProject ?? null
},
// @todo Re-enable when Proxy is ready for public release.
// {
// label: 'Proxy',
// method: ServiceId.Proxy,
// value: project?.serviceStatusForProxy ?? null
// },
{
label: 'Sites',
method: ServiceId.Sites,
value: project?.serviceStatusForSites ?? null
},
{
label: 'Storage',
method: ServiceId.Storage,
value: project?.serviceStatusForStorage ?? null
},
{
label: 'TablesDB',
method: ServiceId.Tablesdb,
value: project?.serviceStatusForTablesdb ?? null
},
{
label: 'Teams',
method: ServiceId.Teams,
value: project?.serviceStatusForTeams ?? null
},
{
label: 'Users',
method: ServiceId.Users,
value: project?.serviceStatusForUsers ?? null
}
// @todo Re-enable when VCS is ready for public release.
// {
// label: 'VCS',
// method: ServiceId.Vcs,
// value: project?.serviceStatusForVcs ?? null
// }
];
return rows.sort((a, b) => a.label.localeCompare(b.label));
}
function createServices() {
const { subscribe, set } = writable({
list: [
{
label: 'Account',
method: ApiService.Account,
value: null
},
{
label: 'Avatars',
method: ApiService.Avatars,
value: null
},
{
label: 'Databases',
method: ApiService.Databases,
value: null
},
{
label: 'Functions',
method: ApiService.Functions,
value: null
},
{
label: 'Locale',
method: ApiService.Locale,
value: null
},
{
label: 'Messaging',
method: ApiService.Messaging,
value: null
},
{
label: 'Storage',
method: ApiService.Storage,
value: null
},
{
label: 'Teams',
method: ApiService.Teams,
value: null
},
{
label: 'Users',
method: ApiService.Users,
value: null
},
{
label: 'GraphQL',
method: ApiService.Graphql,
value: null
}
]
list: projectServiceRows(null)
});
return {
subscribe,
set,
load: (project: Models.Project) => {
const list = [
{
label: 'Account',
method: ApiService.Account,
value: project.serviceStatusForAccount
},
{
label: 'Avatars',
method: ApiService.Avatars,
value: project.serviceStatusForAvatars
},
{
label: 'Databases',
method: ApiService.Databases,
value: project.serviceStatusForDatabases
},
{
label: 'Functions',
method: ApiService.Functions,
value: project.serviceStatusForFunctions
},
{
label: 'Locale',
method: ApiService.Locale,
value: project.serviceStatusForLocale
},
{
label: 'Messaging',
method: ApiService.Messaging,
value: project.serviceStatusForMessaging
},
{
label: 'Storage',
method: ApiService.Storage,
value: project.serviceStatusForStorage
},
{
label: 'Teams',
method: ApiService.Teams,
value: project.serviceStatusForTeams
},
{
label: 'Users',
method: ApiService.Users,
value: project.serviceStatusForUsers
}
];
set({ list });
set({ list: projectServiceRows(project) });
}
};
}
@@ -7,6 +7,7 @@
import { invalidate } from '$app/navigation';
import { Dependencies } from '$lib/constants';
import UpdateName from './updateName.svelte';
import UpdateProtocols from './updateProtocols.svelte';
import UpdateServices from './updateServices.svelte';
import UpdateInstallations from './updateInstallations.svelte';
import DeleteProject from './deleteProject.svelte';
@@ -89,6 +90,7 @@
{#if $canWriteProjects}
<UpdateName />
<UpdateLabels />
<UpdateProtocols />
<UpdateServices />
<UpdateInstallations {...data.installations} limit={data.limit} offset={data.offset} />
<UpdateVariables
@@ -0,0 +1,242 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { CardGrid } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { InputSwitch } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { protocols, type Protocol } from '$lib/stores/project-protocols';
import { sdk } from '$lib/stores/sdk';
import { project } from '../store';
import Button from '$lib/elements/forms/button.svelte';
import { Dialog, Divider, Layout, Spinner } from '@appwrite.io/pink-svelte';
import { SvelteSet } from 'svelte/reactivity';
import { ProtocolId } from '@appwrite.io/console';
import { get } from 'svelte/store';
let isUpdatingAllProtocols = $state(false);
let showUpdateProtocolDialog = $state(false);
let updateProtocolsEnabledMode = $state<boolean | null>(null);
let apiProtocolUpdates = new SvelteSet<ProtocolId>();
const protocolDescriptions: Record<ProtocolId, string> = {
[ProtocolId.Rest]: 'Standard HTTP API requests from client SDKs.',
[ProtocolId.Graphql]: 'GraphQL API access for queries and mutations.',
[ProtocolId.Websocket]: 'Realtime subscriptions over WebSocket connections.'
};
const isAnyProtocolUpdating = $derived(apiProtocolUpdates.size > 0);
const isAnyUpdateInProgress = $derived(isUpdatingAllProtocols || isAnyProtocolUpdating);
const allProtocolsEnabled = $derived.by(() => {
if (isAnyUpdateInProgress) return false;
return $protocols.list.every((protocol) => protocol.value);
});
const allProtocolsDisabled = $derived.by(() => {
if (isAnyUpdateInProgress) return false;
return $protocols.list.every((protocol) => !protocol.value);
});
const shouldDisableEnableAllButton = $derived(isAnyUpdateInProgress || allProtocolsEnabled);
const shouldDisableDisableAllButton = $derived(isAnyUpdateInProgress || allProtocolsDisabled);
async function protocolUpdate(protocol: Protocol) {
apiProtocolUpdates.add(protocol.method);
try {
await sdk.forProject($project.region, $project.$id).project.updateProtocolStatus({
protocolId: protocol.method,
enabled: protocol.value
});
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message: `${protocol.label} protocol has been ${
protocol.value ? 'enabled' : 'disabled'
}`
});
trackEvent(Submit.ProjectService, {
method: protocol.method,
value: protocol.value
});
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.ProjectService);
} finally {
apiProtocolUpdates.delete(protocol.method);
}
}
async function toggleAllProtocols(status: boolean) {
isUpdatingAllProtocols = true;
try {
const projectSdk = sdk.forProject($project.region, $project.$id);
for (const protocol of get(protocols).list) {
if (protocol.value === status) continue;
await projectSdk.project.updateProtocolStatus({
protocolId: protocol.method,
enabled: status
});
}
await invalidate(Dependencies.PROJECT);
addNotification({
type: 'success',
message:
'All protocols for ' +
$project.name +
' have been ' +
(status ? 'enabled.' : 'disabled.')
});
trackEvent(Submit.ProjectService);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.ProjectService);
} finally {
isUpdatingAllProtocols = false;
showUpdateProtocolDialog = false;
updateProtocolsEnabledMode = null;
}
}
const dialogDetails = $derived.by(() => {
if (updateProtocolsEnabledMode) {
return {
title: 'Enable all protocols',
message: 'All project protocols will be enabled.',
actionButton: 'Enable all'
};
} else {
return {
title: 'Disable all protocols',
message:
'Are you sure you want to disable all protocols? This will disable client access over those protocols until they are re-enabled.',
actionButton: 'Disable all'
};
}
});
$effect(() => protocols.load($project));
</script>
<CardGrid>
<svelte:fragment slot="title">Protocols</svelte:fragment>
Choose which protocols clients can use to access your project. Disabled protocols remain unavailable
until re-enabled.
<svelte:fragment slot="aside">
<div class="protocols-list">
<div class="protocol-toolbar">
<Layout.Stack direction="row" alignItems="center" gap="s">
<Button
extraCompact
on:click={() => {
showUpdateProtocolDialog = true;
updateProtocolsEnabledMode = true;
}}
disabled={shouldDisableEnableAllButton}>Enable all</Button>
<span style:height="20px">
<Divider vertical />
</span>
<Button
extraCompact
on:click={() => {
showUpdateProtocolDialog = true;
updateProtocolsEnabledMode = false;
}}
disabled={shouldDisableDisableAllButton}>Disable all</Button>
</Layout.Stack>
</div>
<Divider />
<div class="protocol-list-content">
<Layout.Stack gap="xs">
{#each $protocols.list as protocol, index}
<div class="protocol-row">
<div class="protocol-control">
<InputSwitch
id={protocol.method}
label={protocol.label}
description={protocolDescriptions[protocol.method]}
bind:value={protocol.value}
on:change={() => protocolUpdate(protocol)}
disabled={apiProtocolUpdates.has(protocol.method)} />
{#if apiProtocolUpdates.has(protocol.method)}
<span class="protocol-spinner">
<Spinner size="s" />
</span>
{/if}
</div>
</div>
{#if index < $protocols.list.length - 1}
<Divider />
{/if}
{/each}
</Layout.Stack>
</div>
</div>
</svelte:fragment>
</CardGrid>
<Dialog title={dialogDetails.title} bind:open={showUpdateProtocolDialog}>
<p class="text" data-private>{dialogDetails.message}</p>
<svelte:fragment slot="footer">
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
<Button text on:click={() => (showUpdateProtocolDialog = false)}>Cancel</Button>
<Button
secondary
submissionLoader
disabled={isUpdatingAllProtocols}
forceShowLoader={isUpdatingAllProtocols}
on:click={() => toggleAllProtocols(updateProtocolsEnabledMode)}>
{dialogDetails.actionButton}
</Button>
</Layout.Stack>
</svelte:fragment>
</Dialog>
<style>
.protocols-list {
max-width: 36rem;
}
.protocol-toolbar {
display: flex;
justify-content: flex-end;
padding-bottom: var(--space-6);
}
.protocol-row {
width: 100%;
}
.protocol-list-content {
padding-top: var(--space-6);
}
.protocol-control {
display: flex;
align-items: center;
gap: var(--space-4);
width: 100%;
}
.protocol-control :global(label) {
flex: 1;
}
.protocol-spinner {
opacity: 0.75;
flex-shrink: 0;
}
</style>
@@ -10,7 +10,8 @@
import { project } from '../store';
import Button from '$lib/elements/forms/button.svelte';
import { Dialog, Divider, Layout, Spinner } from '@appwrite.io/pink-svelte';
import type { ApiService } from '@appwrite.io/console';
import type { ServiceId } from '@appwrite.io/console';
import { get } from 'svelte/store';
import { SvelteSet } from 'svelte/reactivity';
@@ -18,7 +19,7 @@
let showUpdateServiceDialog = $state(false);
let updateServicesEnabledMode = $state<boolean | null>(null);
let apiServiceUpdates = new SvelteSet<ApiService>();
let apiServiceUpdates = new SvelteSet<ServiceId>();
const isAnyServiceUpdating = $derived(apiServiceUpdates.size > 0);
const isAnyUpdateInProgress = $derived(isUpdatingAllServices || isAnyServiceUpdating);
@@ -41,10 +42,9 @@
apiServiceUpdates.add(service.method);
try {
await sdk.forConsole.projects.updateServiceStatus({
projectId: $project.$id,
service: service.method,
status: service.value
await sdk.forProject($project.region, $project.$id).project.updateServiceStatus({
serviceId: service.method,
enabled: service.value
});
await invalidate(Dependencies.PROJECT);
@@ -74,10 +74,14 @@
isUpdatingAllServices = true;
try {
await sdk.forConsole.projects.updateServiceStatusAll({
projectId: $project.$id,
status
});
const projectSdk = sdk.forProject($project.region, $project.$id);
for (const s of get(services).list) {
if (s.value === status) continue;
await projectSdk.project.updateServiceStatus({
serviceId: s.method,
enabled: status
});
}
await invalidate(Dependencies.PROJECT);
@@ -30,10 +30,10 @@
name: $webhook.name,
events: $webhook.events,
url: $webhook.url,
security: $webhook.security,
tls: $webhook.tls,
enabled,
httpUser: $webhook.httpUser || undefined,
httpPass: $webhook.httpPass || undefined
authUsername: $webhook.authUsername || undefined,
authPassword: $webhook.authPassword || undefined
});
await invalidate(Dependencies.WEBHOOK);
addNotification({
@@ -2,25 +2,47 @@
import { invalidate } from '$app/navigation';
import { page } from '$app/state';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import Confirm from '$lib/components/confirm.svelte';
import Modal from '$lib/components/modal.svelte';
import { Secret } from '$lib/components';
import { Dependencies } from '$lib/constants';
import { Button, InputPassword } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { webhook } from './store';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
import type { Models } from '@appwrite.io/console';
import { get } from 'svelte/store';
import { webhook as webhookStore } from './store';
type WebhooksWithCustomSecret = {
updateSecret(params: { webhookId: string; secret?: string }): Promise<Models.Webhook>;
};
export let show = false;
const projectId = page.params.project;
let secret = '';
let revealedSecret = '';
$: if (!show) {
secret = '';
revealedSecret = '';
}
async function regenerate() {
try {
await sdk.forProject(page.params.region, projectId).webhooks.updateSignature({
webhookId: $webhook.$id
const currentWebhook = get(webhookStore);
const customSecret = secret.trim();
const updatedWebhook = await (
sdk.forProject(page.params.region, projectId).webhooks as WebhooksWithCustomSecret
).updateSecret({
webhookId: currentWebhook.$id,
secret: customSecret || undefined
});
await invalidate(Dependencies.WEBHOOK);
show = false;
revealedSecret = customSecret || updatedWebhook.secret;
addNotification({
type: 'success',
message: 'Key has been regenerated'
message: customSecret ? 'Webhook secret updated.' : 'Webhook secret rotated.'
});
trackEvent(Submit.WebhookUpdateSignature);
} catch (error) {
@@ -33,7 +55,38 @@
}
</script>
<Confirm title="Regenerate Key" bind:open={show} onSubmit={regenerate} action="Regenerate">
Are you sure you want to generate a new Signature key?
<b>You will not be able to recover the current key.</b>
</Confirm>
<Modal title="Rotate Webhook Secret" bind:show onSubmit={regenerate}>
<Layout.Stack gap="l">
{#if revealedSecret}
<Typography.Text>
This secret is only shown once after webhook creation or secret rotation. Copy it
now.
</Typography.Text>
<Secret label="Secret" copyEvent="signature" bind:value={revealedSecret} />
{:else}
<Typography.Text>
Leave this empty to rotate the webhook secret automatically, or enter a value to set
a custom secret.
</Typography.Text>
<Typography.Text variant="m-400">
Used to validate incoming webhook payloads.
</Typography.Text>
<InputPassword
id="webhook-secret"
label="Secret"
placeholder="Leave empty to auto-generate"
minlength={0}
autocomplete
bind:value={secret} />
{/if}
</Layout.Stack>
<svelte:fragment slot="footer">
{#if revealedSecret}
<Button on:click={() => (show = false)}>Done</Button>
{:else}
<Button text on:click={() => (show = false)}>Cancel</Button>
<Button submit>{secret.trim() ? 'Set custom secret' : 'Rotate secret'}</Button>
{/if}
</svelte:fragment>
</Modal>
@@ -31,10 +31,10 @@
name: $webhook.name,
events: Array.from($eventSet),
url: $webhook.url,
security: $webhook.security,
tls: $webhook.tls,
enabled: true,
httpUser: $webhook.httpUser || undefined,
httpPass: $webhook.httpPass || undefined
authUsername: $webhook.authUsername || undefined,
authPassword: $webhook.authPassword || undefined
});
await invalidate(Dependencies.WEBHOOK);
areEventsDisabled = true;
@@ -24,10 +24,10 @@
name,
events: $webhook.events,
url: $webhook.url,
security: $webhook.security,
tls: $webhook.tls,
enabled: true,
httpUser: $webhook.httpUser || undefined,
httpPass: $webhook.httpPass || undefined
authUsername: $webhook.authUsername || undefined,
authPassword: $webhook.authPassword || undefined
});
await invalidate(Dependencies.WEBHOOK);
@@ -12,14 +12,14 @@
import { Selector, Typography } from '@appwrite.io/pink-svelte';
const projectId = page.params.project;
let httpUser: string = null;
let httpPass: string = null;
let security = false;
let authUsername: string = null;
let authPassword: string = null;
let tls = false;
onMount(async () => {
httpUser ??= $webhook.httpUser;
httpPass ??= $webhook.httpPass;
security = $webhook.security;
authUsername ??= $webhook.authUsername;
authPassword ??= $webhook.authPassword;
tls = $webhook.tls;
});
async function updateSecurity() {
@@ -29,10 +29,10 @@
name: $webhook.name,
events: $webhook.events,
url: $webhook.url,
security,
tls,
enabled: true,
httpUser: httpUser || undefined,
httpPass: httpPass || undefined
authUsername: authUsername || undefined,
authPassword: authPassword || undefined
});
await invalidate(Dependencies.WEBHOOK);
addNotification({
@@ -60,18 +60,22 @@
<Typography.Title size="s">HTTP Authentication</Typography.Title>
<p class="text">Use to secure your endpoint from untrusted sources.</p>
</div>
<InputText label="User" id="user" placeholder="Enter username" bind:value={httpUser} />
<InputText
label="User"
id="user"
placeholder="Enter username"
bind:value={authUsername} />
<InputPassword
label="Password"
id="password"
minlength={0}
placeholder="Enter password"
bind:value={httpPass} />
bind:value={authPassword} />
<Selector.Checkbox
id="security"
id="tls"
label="Certificate verification (SSL/TLS)"
bind:checked={security}
bind:checked={tls}
description="Placeholder" />
<!-- <span class="u-color-text-danger">Warning:</span> Untrusted or self-signed certificates
may not be secure.
@@ -85,9 +89,9 @@
<svelte:fragment slot="actions">
<Button
disabled={httpUser === $webhook.httpUser &&
httpPass === $webhook.httpPass &&
security === $webhook.security}
disabled={authUsername === $webhook.authUsername &&
authPassword === $webhook.authPassword &&
tls === $webhook.tls}
submit>
Update
</Button>
@@ -1,26 +1,25 @@
<script lang="ts">
import { CardGrid, Secret } from '$lib/components';
import { CardGrid } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Link } from '@appwrite.io/pink-svelte';
import { Alert, Link } from '@appwrite.io/pink-svelte';
import Regenerate from './regenerate.svelte';
import { webhook } from './store';
import { Click, trackEvent } from '$lib/actions/analytics';
let showRegenerate = false;
</script>
<CardGrid>
<svelte:fragment slot="title">Signature key</svelte:fragment>
Add the Signature Key to the X-Appwrite-Webhook-Signature header to validate your webhooks.
<svelte:fragment slot="title">Webhook secret</svelte:fragment>
Used to validate incoming webhook payloads with the `X-Appwrite-Webhook-Signature` header.
<Link.Anchor
href="https://appwrite.io/docs/advanced/platform/webhooks#verification"
target="_blank"
rel="noopener noreferrer"
class="link">Learn more</Link.Anchor>
<svelte:fragment slot="aside">
<div>
<Secret label="Key" copyEvent="signature" bind:value={$webhook.signatureKey} />
</div>
<Alert.Inline status="info">
This secret is only shown once after webhook creation or secret rotation.
</Alert.Inline>
</svelte:fragment>
<svelte:fragment slot="actions">
<Button
@@ -29,7 +28,7 @@
trackEvent(Click.SettingsWebhookUpdateSignatureClick);
}}
secondary
submit>Regenerate key</Button>
submit>Rotate secret</Button>
</svelte:fragment>
</CardGrid>
@@ -24,10 +24,10 @@
name: $webhook.name,
events: $webhook.events,
url,
security: $webhook.security,
tls: $webhook.tls,
enabled: true,
httpUser: $webhook.httpUser || undefined,
httpPass: $webhook.httpPass || undefined
authUsername: $webhook.authUsername || undefined,
authPassword: $webhook.authPassword || undefined
});
await invalidate(Dependencies.WEBHOOK);
@@ -1,8 +1,10 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import Modal from '$lib/components/modal.svelte';
import { Secret } from '$lib/components';
import { Wizard } from '$lib/layout';
import { Layout } from '@appwrite.io/pink-svelte';
import { Layout, Typography } from '@appwrite.io/pink-svelte';
import Form from '$lib/elements/forms/form.svelte';
import { goto } from '$app/navigation';
import { ID } from '@appwrite.io/console';
@@ -15,15 +17,28 @@
import Button from '$lib/elements/forms/button.svelte';
let showExitModal = false;
let showSecretModal = false;
let createdWebhookId = '';
let createdSecret = '';
let name = '',
events: string[] = [],
url = '',
security = true,
httpUser = '',
httpPass = '';
tls = true,
authUsername = '',
authPassword = '';
export let data;
async function openWebhook() {
showSecretModal = false;
if (!createdWebhookId) return;
await goto(
`${base}/project-${page.params.region}-${page.params.project}/settings/webhooks/${createdWebhookId}`
);
}
async function create() {
try {
const webhook = await sdk
@@ -33,21 +48,28 @@
name,
events,
url,
security,
tls,
enabled: true,
httpUser: httpUser || undefined,
httpPass: httpPass || undefined
authUsername: authUsername || undefined,
authPassword: authPassword || undefined
});
addNotification({
message: 'Webhook has been created',
message: 'Webhook created. Secret ready to copy.',
type: 'success'
});
trackEvent(Submit.WebhookCreate, {
events: events
});
goto(
`${base}/project-${page.params.region}-${page.params.project}/settings/webhooks/${webhook.$id}`
);
createdWebhookId = webhook.$id;
createdSecret = webhook.secret;
if (createdSecret) {
showSecretModal = true;
return;
}
await openWebhook();
} catch (error) {
addNotification({
type: 'error',
@@ -68,7 +90,7 @@
<Layout.Stack gap="xxl">
<Step1 bind:name bind:url />
<Step2 bind:events />
<Step3 bind:httpUser bind:httpPass bind:security />
<Step3 bind:authUsername bind:authPassword bind:tls />
</Layout.Stack>
<svelte:fragment slot="footer">
@@ -76,3 +98,16 @@
</svelte:fragment>
</Wizard>
</Form>
<Modal title="Webhook Created" bind:show={showSecretModal} onSubmit={openWebhook}>
<Layout.Stack gap="l">
<Typography.Text>
This secret is only shown once after webhook creation or secret rotation. Copy it now.
</Typography.Text>
<Secret label="Secret" copyEvent="signature" bind:value={createdSecret} />
</Layout.Stack>
<svelte:fragment slot="footer">
<Button submit>Continue</Button>
</svelte:fragment>
</Modal>
@@ -3,9 +3,9 @@
import Button from '$lib/elements/forms/button.svelte';
import { Alert, Fieldset, Layout, Typography } from '@appwrite.io/pink-svelte';
export let httpUser: string;
export let httpPass: string;
export let security: boolean;
export let authUsername: string;
export let authPassword: string;
export let tls: boolean;
</script>
<Fieldset legend="Security">
@@ -13,10 +13,10 @@
<Layout.Stack gap="s">
<InputChoice
type="switchbox"
id="Security"
id="tls"
label="Certificate verification (SSL/TLS)"
bind:value={security} />
{#if !security}
bind:value={tls} />
{#if !tls}
<Alert.Inline
status="warning"
title="Untrusted or self-signed certificates may not be secure.">
@@ -34,13 +34,17 @@
<Typography.Text
>Use to secure your endpoint from untrusted sources.</Typography.Text>
</div>
<InputText label="User" id="user" placeholder="Enter username" bind:value={httpUser} />
<InputText
label="User"
id="user"
placeholder="Enter username"
bind:value={authUsername} />
<InputPassword
label="Password"
id="password"
placeholder="Enter password"
minlength={0}
bind:value={httpPass} />
bind:value={authPassword} />
</Layout.Stack>
</Layout.Stack>
</Fieldset>