feat: svix integration (#3814)

* Add create_webhook page

* Make small fixes

* Add token and change layout

* Rename to create-webhook

* Change query param to domain
This commit is contained in:
wendrul
2024-11-20 19:17:35 +01:00
committed by GitHub
parent a23cd4f9e7
commit 68f781ea6f
2 changed files with 184 additions and 28 deletions
+35 -28
View File
@@ -27,6 +27,7 @@
export let kinds: Script['kind'][] = ['script']
export let disabled = false
export let allowRefresh = false
export let allowEdit = true
let items: { value: string; label: string }[] = []
let drawerViewer: Drawer
@@ -127,14 +128,16 @@
{#if scriptPath !== undefined && scriptPath !== ''}
{#if itemKind == 'flow'}
<div class="flex gap-2">
<Button
endIcon={{ icon: ExternalLink }}
target="_blank"
color="light"
size="xs"
variant="border"
href="{base}/flows/edit/{scriptPath}">Edit</Button
>
{#if allowEdit}
<Button
endIcon={{ icon: ExternalLink }}
target="_blank"
color="light"
size="xs"
variant="border"
href="{base}/flows/edit/{scriptPath}">Edit</Button
>
{/if}
<Button
color="light"
size="xs"
@@ -148,16 +151,18 @@
</div>
{:else if itemKind == 'app'}
<div class="flex gap-2">
<Button
startIcon={{ icon: Pen }}
target="_blank"
color="light"
size="xs"
href="{base}/apps/edit/{scriptPath}"
variant="border"
>
Edit
</Button>
{#if allowEdit}
<Button
startIcon={{ icon: Pen }}
target="_blank"
color="light"
size="xs"
href="{base}/apps/edit/{scriptPath}"
variant="border"
>
Edit
</Button>
{/if}
<Button
color="light"
size="xs"
@@ -171,16 +176,18 @@
</div>
{:else}
<div class="flex gap-2">
<Button
startIcon={{ icon: Pen }}
target="_blank"
color="light"
size="xs"
href="{base}/scripts/edit/{scriptPath}"
variant="border"
>
Edit
</Button>
{#if allowEdit}
<Button
startIcon={{ icon: Pen }}
target="_blank"
color="light"
size="xs"
href="{base}/scripts/edit/{scriptPath}"
variant="border"
>
Edit
</Button>
{/if}
<Button
color="light"
size="xs"
@@ -0,0 +1,149 @@
<script lang="ts">
import PageHeader from '$lib/components/PageHeader.svelte'
import ScriptPicker from '$lib/components/ScriptPicker.svelte'
import CenteredPage from '$lib/components/CenteredPage.svelte'
import { Button } from '$lib/components/common'
import { page } from '$app/stores'
import { userStore, workspaceStore } from '$lib/stores'
import { sendUserToast } from '$lib/toast'
import UserSettings from '$lib/components/UserSettings.svelte'
import { generateRandomString } from '$lib/utils'
import Tooltip from '$lib/components/Tooltip.svelte'
let itemPath: string | undefined = undefined
let itemKind: 'script' | 'flow' | 'app' = 'script'
let clientName = $page.url.searchParams.get('domain') ?? undefined
let redirectURI = $page.url.searchParams.get('redirect_uri')
let userSettings: UserSettings
let token: string | undefined = undefined
let scopes: string[] = []
$: updateTokenAndScope(itemPath)
async function createWebhook(path: string, kind: 'script' | 'flow' | 'app', token: string) {
try {
let webhook: string
switch (kind) {
case 'script':
webhook = `${$page.url.origin}/api/w/${$workspaceStore}/jobs/run/p/${path}`
redirectSuccess(webhook, token)
break
case 'flow':
webhook = `${$page.url.origin}/api/w/${$workspaceStore}/jobs/run/f/${path}`
redirectSuccess(webhook, token)
break
default:
sendUserToast('Invalid item type', true)
break
}
} catch (error) {
sendUserToast(error, true)
}
}
function redirectSuccess(webhook: string, token: string) {
const webhook_url = new URL(webhook)
webhook_url.searchParams.append('token', token)
const url = new URL(redirectURI!)
url.searchParams.append('webhook_url', webhook_url.toString())
window.location.href = url.toString()
}
function redirectCancel(e: string): void {
const url = new URL(redirectURI!)
url.searchParams.append('error', e)
window.location.href = url.toString()
}
function updateTokenAndScope(scriptPath: string | undefined) {
if (!scriptPath) {
scopes = []
token = undefined
return
}
token = undefined
scopes = [`run:${itemKind}/${scriptPath}`]
}
</script>
<UserSettings
bind:this={userSettings}
on:tokenCreated={(e) => {
token = e.detail
}}
newTokenLabel={`webhook-${$userStore?.username ?? 'superadmin'}-${generateRandomString(4)}`}
{scopes}
/>
<CenteredPage>
<div class="flex items-center justify-center h-screen">
<div class="flex flex-col gap-5">
<div class="flex flex-row gap-3 items-center justify-center">
<PageHeader title={`${clientName} wants to create a webhook`} />
</div>
<div class="flex flex-row gap-3 items-center">
<div
class="flex items-center justify-center w-10 h-10 border-2 border-blue-500 text-blue-500 font-bold rounded-full"
>
1
</div>
<h2>Select the script or flow to be triggered by the webhook</h2>
</div>
<div class="flex flex-row items-center justify-center w-100%">
<div class="flex flex-col items-center gap-3 w-full">
<ScriptPicker
allowEdit={false}
allowFlow={true}
allowRefresh={true}
bind:scriptPath={itemPath}
bind:itemKind
/>
<h4>or</h4>
<div class="flex flex-row gap-2">
<Button size="xs" color="light" variant="border" target="_blank" href="/scripts/add">Create New Script</Button>
<Button size="xs" color="light" variant="border" target="_blank" href="/flows/add?nodraft=true">Create New Flow</Button>
</div>
</div>
</div>
<div class="flex flex-row gap-3 items-center mt-3">
<div
class="flex items-center justify-center w-10 h-10 border-2 border-blue-500 text-blue-500 font-bold rounded-full"
>
2
</div>
<h2>Create a token for this webhook</h2>
<Tooltip customSize="150%">
If you generate a token, it will have a scope such that it can only be used to
trigger this {itemKind}. It is safe to share as it cannot be used to impersonate you.
Otherwise you may enter another token but beware that it will be shared with the external
application.
</Tooltip>
</div>
<div class="flex flex-row items-center justify-center">
<div class="flex flex-col items-center gap-5 w-100%">
<div class="flex flex-row items-center gap-2">
<input
bind:value={token}
disabled={itemPath == undefined}
placeholder="enter or generate token"
class="!text-md"
/>
<Button size="md" disabled={itemPath == undefined} on:click={userSettings.openDrawer}>
Generate webhook-specific Token
</Button>
</div>
<div class="flex flex-row gap-2 mt-10">
<Button
disabled={!itemPath || !token || token === ''}
on:click={() => itemPath && token && createWebhook(itemPath, itemKind, token)}
>Approve and share with {clientName}</Button
>
<Button color="red" on:click={() => redirectCancel('user_canceled')}>Cancel</Button>
</div>
</div>
</div>
</div>
</div>
</CenteredPage>