fix(frontend): improve UI for email triggers (#4243)

* fix(frontend): improve UI for email triggers

* fix: nits
This commit is contained in:
HugoCasa
2024-08-15 10:06:35 +02:00
committed by GitHub
parent 5416254aa1
commit 6c9e32af10
6 changed files with 166 additions and 76 deletions
@@ -1,7 +1,7 @@
<script lang="ts">
import { Tabs, Tab, TabContent, Button } from '$lib/components/common'
import { copyToClipboard } from '$lib/utils'
import { CalendarCheck2, Clipboard, Terminal, Webhook } from 'lucide-svelte'
import { CalendarCheck2, Clipboard, MailIcon, Terminal, Webhook } from 'lucide-svelte'
import { Highlight } from 'svelte-highlight'
import { yaml } from 'svelte-highlight/languages'
import json from 'svelte-highlight/languages/json'
@@ -9,7 +9,7 @@
import YAML from 'yaml'
import HighlightTheme from '../HighlightTheme.svelte'
export let triggerSelected: 'webhooks' | 'schedule' | 'cli' = 'webhooks'
export let triggerSelected: 'webhooks' | 'email' | 'schedule' | 'cli' = 'webhooks'
export let flow_json: any | undefined = undefined
export let hasStepDetails: boolean = false
@@ -66,6 +66,12 @@
Schedules
</span>
</Tab>
<Tab value="email">
<span class="flex flex-row gap-2 items-center">
<MailIcon size={14} />
Email
</span>
</Tab>
<Tab value="cli">
<span class="flex flex-row gap-2 items-center">
<Terminal size={14} />
@@ -78,6 +84,8 @@
<div class="h-full overflow-auto">
{#if triggerSelected === 'webhooks'}
<slot name="webhooks" />
{:else if triggerSelected === 'email'}
<slot name="email" />
{:else if triggerSelected === 'schedule'}
<slot name="schedule" />
{:else if triggerSelected === 'cli'}
@@ -33,6 +33,7 @@
{hasStepDetails}
>
<slot slot="webhooks" name="webhooks" />
<slot slot="email" name="email" />
<slot slot="schedule" name="schedule" />
<slot slot="cli" name="cli" />
<slot slot="details" name="details" />
@@ -57,6 +58,7 @@
<TabContent value="detail" class="flex flex-col flex-1 h-full">
<DetailPageDetailPanel bind:triggerSelected bind:selected {isOperator} {hasStepDetails}>
<slot slot="webhooks" name="webhooks" />
<slot slot="email" name="email" />
<slot slot="schedule" name="schedule" />
<slot slot="cli" name="cli" />
<slot slot="details" name="details" />
@@ -0,0 +1,129 @@
<script lang="ts">
import Tooltip from '$lib/components/Tooltip.svelte'
import { enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
import { Button } from '$lib/components/common'
import { SCRIPT_VIEW_SHOW_CREATE_TOKEN_BUTTON } from '$lib/consts'
import UserSettings from '../UserSettings.svelte'
import ClipboardPanel from './ClipboardPanel.svelte'
import { generateRandomString } from '$lib/utils'
import HighlightTheme from '../HighlightTheme.svelte'
import Alert from '../common/alert/Alert.svelte'
import { SettingService } from '$lib/gen'
import { base32 } from 'rfc4648'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import { AlertTriangle } from 'lucide-svelte'
let userSettings: UserSettings
export let token: string
export let scopes: string[] = []
export let isFlow: boolean = false
export let hash: string | undefined = undefined
export let path: string
let emailDomain: string | null = null
async function getEmailDomain() {
emailDomain =
((await SettingService.getGlobal({
key: 'email_domain'
})) as any) ?? null
}
getEmailDomain()
let requestType: 'hash' | 'path' = 'path'
function emailAddress() {
const pathOrHash = requestType === 'hash' ? hash : path.replaceAll('/', '.')
const plainPrefix = `${$workspaceStore}+${
(requestType === 'hash' ? 'hash.' : isFlow ? 'flow.' : '') + pathOrHash
}+${token}`
const encodedPrefix = base32
.stringify(new TextEncoder().encode(plainPrefix), {
pad: false
})
.toLowerCase()
return `${pathOrHash}+${encodedPrefix}@${emailDomain}`
}
</script>
<HighlightTheme />
<UserSettings
bind:this={userSettings}
on:tokenCreated={(e) => {
token = e.detail
}}
newTokenLabel={`${$userStore?.username ?? 'superadmin'}-${generateRandomString(4)}`}
{scopes}
/>
<div class="p-2 flex flex-col w-full gap-4">
{#if emailDomain}
{#if SCRIPT_VIEW_SHOW_CREATE_TOKEN_BUTTON}
<div class="my-2">
<div class="flex flex-row justify-between gap-2">
<input
bind:value={token}
placeholder="paste your token here once created to alter examples below"
class="!text-xs"
/>
<Button size="xs" color="light" variant="border" on:click={userSettings.openDrawer}>
Create an Email-specific Token
<Tooltip light>
The token will have a scope such that it can only be used to trigger this script. It
is safe to share as it cannot be used to impersonate you.
</Tooltip>
</Button>
</div>
{#if token === 'TOKEN_TO_CREATE'}
<div class="flex flex-row gap-1 text-xs text-red-500 items-center mt-1">
<AlertTriangle size="12" />
Create/input a valid token before copying the email address below
</div>
{/if}
</div>
{/if}
{#if !isFlow}
<div class="flex flex-col gap-2">
<div class="flex flex-row justify-between">
<div class="text-xs font-semibold flex flex-row items-center">Call method</div>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={requestType}>
<ToggleButton label="By path" value="path" />
<ToggleButton label="By hash" value="hash" />
</ToggleButtonGroup>
</div>
</div>
{/if}
<div class="flex flex-col gap-4">
{#key requestType}
{#key token}
<div class="flex flex-col gap-2">
<ClipboardPanel title="Email address" content={emailAddress()} />
</div>
{/key}
{/key}
<Alert title="Email trigger" size="xs">
To trigger the job by email, send an email to the address above. The job will receive two
arguments: `raw_email` containing the raw email as string, and `parsed_email` containing the
parsed email as an object.
</Alert>
</div>
{:else}
<div>
<Alert title="Email triggers are disabled" size="xs" type="warning">
Ask an instance superadmin to setup the instance for email triggering (<a
target="_blank"
href="https://windmill.dev/docs/advanced/email_triggers">docs</a
>) and to set the email domain in the instance settings.
</Alert>
</div>
{/if}
{#if !$enterpriseLicense}
<Alert title="Community Edition limitations" type="warning" size="xs">
Email triggers on Windmill Community Edition are limited to 100 emails per day.
</Alert>
{/if}
</div>
@@ -18,11 +18,7 @@
import ClipboardPanel from './ClipboardPanel.svelte'
import { copyToClipboard, generateRandomString } from '$lib/utils'
import HighlightTheme from '../HighlightTheme.svelte'
import Alert from '../common/alert/Alert.svelte'
import { SettingService } from '$lib/gen'
import { base } from '$lib/base'
import { base32 } from 'rfc4648'
let userSettings: UserSettings
export let token: string
@@ -46,15 +42,6 @@
}
}
let emailDomain: string | null = null
async function getEmailDomain() {
emailDomain =
((await SettingService.getGlobal({
key: 'email_domain'
})) as any) ?? null
}
getEmailDomain()
$: webhooks = isFlow ? computeFlowWebhooks(path) : computeScriptWebhooks(hash, path)
function computeScriptWebhooks(hash: string | undefined, path: string) {
@@ -97,10 +84,6 @@
requestType = 'hash'
}
$: if (webhookType === 'sync' && selectedTab === 'email') {
webhookType = 'async'
}
$: url =
webhooks[webhookType][requestType] +
(tokenType === 'query'
@@ -127,19 +110,6 @@
return headers
}
function emailAddress() {
const pathOrHash = requestType === 'hash' ? hash : path.replaceAll('/', '.')
const plainPrefix = `${$workspaceStore}+${
(requestType === 'hash' ? 'hash.' : isFlow ? 'flow.' : '') + pathOrHash
}+${token}`
const encodedPrefix = base32
.stringify(new TextEncoder().encode(plainPrefix), {
pad: false
})
.toLowerCase()
return `${pathOrHash}+${encodedPrefix}@${emailDomain}`
}
function fetchCode() {
if (webhookType === 'sync') {
return `
@@ -293,7 +263,6 @@ done`
label="Sync"
value="sync"
tooltip="Triggers the execution, wait for the job to complete and return it as a response."
disabled={selectedTab === 'email'}
/>
</ToggleButtonGroup>
</div>
@@ -324,15 +293,13 @@ done`
/>
</ToggleButtonGroup>
</div>
{#if selectedTab !== 'email'}
<div class="flex flex-row justify-between">
<div class="text-xs font-semibold flex flex-row items-center">Token configuration</div>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={tokenType}>
<ToggleButton label="Token in Headers" value="headers" />
<ToggleButton label="Token in Query" value="query" />
</ToggleButtonGroup>
</div>
{/if}
<div class="flex flex-row justify-between">
<div class="text-xs font-semibold flex flex-row items-center">Token configuration</div>
<ToggleButtonGroup class="h-[30px] w-auto" bind:selected={tokenType}>
<ToggleButton label="Token in Headers" value="headers" />
<ToggleButton label="Token in Query" value="query" />
</ToggleButtonGroup>
</div>
</div>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -342,7 +309,6 @@ done`
<Tab value="curl" size="xs">Curl</Tab>
{/if}
<Tab value="fetch" size="xs">Fetch</Tab>
<Tab value="email" size="xs">Email</Tab>
<svelte:fragment slot="content">
{#key token}
@@ -401,39 +367,6 @@ done`
{/key}{/key}{/key}{/key}
{/key}
</TabContent>
<TabContent value="email">
{#if emailDomain}
<div class="flex flex-col gap-4">
{#key args}
{#key requestType}
{#key webhookType}
{#key tokenType}
{#key token}
<div class="flex flex-col gap-2">
<ClipboardPanel title="Email address" content={emailAddress()} />
</div>
{/key}
{/key}
{/key}
{/key}
{/key}
<Alert title="Email triggers" size="xs">
To trigger the job by email, send an email to the address above. The job will
receive two arguments: `raw_email` containing the raw email as string, and
`parsed_email` containing the parsed email as an object.
</Alert>
</div>
{:else}
<div>
<Alert title="Email triggers are disabled" size="xs" kind="danger">
Ask an instance superadmin to setup the instance for email triggering (<a
target="_blank"
href="https://windmill.dev/docs/advanced/email_triggers">docs</a
>) and to set the email domain in the instance settings.
</Alert>
</div>
{/if}
</TabContent>
{/key}
</svelte:fragment>
</Tabs>
@@ -53,6 +53,7 @@
import { loadFlowSchedule, type Schedule } from '$lib/components/flows/scheduleUtils'
import GfmMarkdown from '$lib/components/GfmMarkdown.svelte'
import FlowHistory from '$lib/components/flows/FlowHistory.svelte'
import EmailTriggerPanel from '$lib/components/details/EmailTriggerPanel.svelte'
let flow: Flow | undefined
let can_write = false
@@ -472,6 +473,14 @@
{args}
/>
</svelte:fragment>
<svelte:fragment slot="email">
<EmailTriggerPanel
bind:token
scopes={[`run:flow/${flow?.path}`]}
path={flow?.path}
isFlow={true}
/>
</svelte:fragment>
<svelte:fragment slot="schedule">
<RunPageSchedules isFlow={true} path={flow.path ?? ''} {can_write} />
</svelte:fragment>
@@ -70,6 +70,7 @@
import PersistentScriptDrawer from '$lib/components/PersistentScriptDrawer.svelte'
import { loadScriptSchedule, type ScriptSchedule } from '$lib/scripts'
import GfmMarkdown from '$lib/components/GfmMarkdown.svelte'
import EmailTriggerPanel from '$lib/components/details/EmailTriggerPanel.svelte'
let script: Script | undefined
let topHash: string | undefined
@@ -668,6 +669,14 @@
{args}
/>
</svelte:fragment>
<svelte:fragment slot="email">
<EmailTriggerPanel
bind:token
scopes={[`run:script/${script?.path}`]}
hash={script.hash}
path={script.path}
/>
</svelte:fragment>
<svelte:fragment slot="schedule">
<RunPageSchedules isFlow={false} path={script.path ?? ''} {can_write} />
</svelte:fragment>