Merge branch 'main' into fr/xyflow
This commit is contained in:
@@ -1383,7 +1383,30 @@ async fn build_args(
|
||||
|
||||
let arg_str = v.get();
|
||||
|
||||
if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
if arg_str.starts_with("\"$ctx:") {
|
||||
let prop = arg_str.trim_start_matches("\"$ctx:").trim_end_matches("\"");
|
||||
let value = match prop {
|
||||
"username" => authed.as_ref().map(|a| {
|
||||
serde_json::to_value(a.username_override.as_ref().unwrap_or(&a.username))
|
||||
}),
|
||||
"email" => authed.as_ref().map(|a| serde_json::to_value(&a.email)),
|
||||
"workspace" => Some(serde_json::to_value(&w_id)),
|
||||
"groups" => authed.as_ref().map(|a| serde_json::to_value(&a.groups)),
|
||||
"author" => Some(serde_json::to_value(&policy.on_behalf_of_email)),
|
||||
_ => {
|
||||
return Err(Error::BadRequest(format!(
|
||||
"context variable {} not allowed",
|
||||
prop
|
||||
)))
|
||||
}
|
||||
};
|
||||
safe_args.insert(
|
||||
k.to_string(),
|
||||
to_raw_value(&value.unwrap_or(Ok(serde_json::Value::Null)).map_err(|e| {
|
||||
Error::InternalErr(format!("failed to serialize ctx variable for {}: {}", k, e))
|
||||
})?),
|
||||
);
|
||||
} else if !arg_str.contains("\"$var:") && !arg_str.contains("\"$res:") {
|
||||
safe_args.insert(k.to_string(), v);
|
||||
} else {
|
||||
safe_args.insert(
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import { userStore } from '$lib/stores'
|
||||
import { get } from 'svelte/store'
|
||||
import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte'
|
||||
import { ctxRegex } from '../../utils'
|
||||
|
||||
// Component props
|
||||
export let id: string
|
||||
@@ -341,7 +342,12 @@
|
||||
allowUserResources.push(k)
|
||||
}
|
||||
} else if (field?.type == 'eval' || (field?.type == 'evalv2' && inputValues[k])) {
|
||||
nonStaticRunnableInputs[k] = await inputValues[k]?.computeExpr()
|
||||
const ctxMatch = field.expr.match(ctxRegex)
|
||||
if (ctxMatch) {
|
||||
nonStaticRunnableInputs[k] = '$ctx:' + ctxMatch[1]
|
||||
} else {
|
||||
nonStaticRunnableInputs[k] = await inputValues[k]?.computeExpr()
|
||||
}
|
||||
if (isEditor && field?.type == 'evalv2' && field.allowUserResources) {
|
||||
allowUserResources.push(k)
|
||||
}
|
||||
|
||||
@@ -363,6 +363,7 @@
|
||||
component.type === 'aggridinfinitecomponentee'
|
||||
? ['offset', 'limit', 'orderBy', 'isDesc', 'search']
|
||||
: []}
|
||||
securedContext
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
export let showOnDemandOnlyToggle = true
|
||||
export let documentationLink: string | undefined = undefined
|
||||
export let markdownTooltip: string | undefined = undefined
|
||||
export let securedContext = false
|
||||
|
||||
const { connectingInput, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
@@ -185,6 +186,7 @@
|
||||
{fixedOverflowWidgets}
|
||||
{recomputeOnInputChanged}
|
||||
{showOnDemandOnlyToggle}
|
||||
{securedContext}
|
||||
/>
|
||||
{:else if componentInput?.type === 'upload'}
|
||||
<UploadInputEditor bind:componentInput {fileUpload} />
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
export let acceptSelf: boolean = false
|
||||
export let recomputeOnInputChanged = true
|
||||
export let showOnDemandOnlyToggle = false
|
||||
export let securedContext = false
|
||||
export let overridenByComponent: string[] = []
|
||||
|
||||
$: finalInputSpecsConfiguration = inputSpecsConfiguration ?? inputSpecs
|
||||
@@ -79,6 +80,7 @@
|
||||
{displayType}
|
||||
{recomputeOnInputChanged}
|
||||
{showOnDemandOnlyToggle}
|
||||
{securedContext}
|
||||
/>
|
||||
{#if deletable}
|
||||
<div class="flex flex-row-reverse -mt-4">
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppEditorContext, AppViewerContext } from '$lib/components/apps/types'
|
||||
import SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
import { buildExtraLib } from '$lib/components/apps/utils'
|
||||
import { buildExtraLib, ctxRegex } from '$lib/components/apps/utils'
|
||||
import { inferDeps } from '../../appUtilsInfer'
|
||||
import { Maximize2, X } from 'lucide-svelte'
|
||||
import { Maximize2, Shield, X } from 'lucide-svelte'
|
||||
import { Drawer } from '$lib/components/common'
|
||||
import { Pane, Splitpanes } from 'svelte-splitpanes'
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import { zIndexes } from '$lib/zIndexes'
|
||||
import Popover from '$lib/components/Popover.svelte'
|
||||
|
||||
export let componentInput: EvalV2AppInput | undefined
|
||||
export let id: string
|
||||
@@ -19,6 +20,7 @@
|
||||
export let acceptSelf: boolean = false
|
||||
export let recomputeOnInputChanged = true
|
||||
export let showOnDemandOnlyToggle = false
|
||||
export let securedContext = false
|
||||
|
||||
const { onchange, worldStore, state, app } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { evalPreview } = getContext<AppEditorContext>('AppEditorContext')
|
||||
@@ -119,6 +121,17 @@
|
||||
inferDepsFromCode(e.detail.code)
|
||||
}}
|
||||
/>
|
||||
{#if securedContext && componentInput?.expr?.match(ctxRegex)}
|
||||
<div class="border bg-surface absolute top-0.5 right-8 p-0.5">
|
||||
<Popover notClickable>
|
||||
<Shield size={12} />
|
||||
<svelte:fragment slot="text">
|
||||
This context variable is securely provided by the backend and cannot be altered by
|
||||
users
|
||||
</svelte:fragment>
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
class="border bg-surface absolute top-0.5 right-2 p-0.5"
|
||||
on:click={() => (fullscreen = true)}><Maximize2 size={12} /></button
|
||||
|
||||
@@ -442,3 +442,5 @@ export function getImageDataURL(imageKind: string | undefined, image: string | u
|
||||
return image
|
||||
}
|
||||
}
|
||||
|
||||
export const ctxRegex = /^ctx\.(workspace|groups|username|email|author)$/
|
||||
|
||||
@@ -44,12 +44,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
let emailDomain: string = "mail." + $page.url.hostname
|
||||
let emailDomain: string | null = null
|
||||
async function getEmailDomain() {
|
||||
emailDomain =
|
||||
((await SettingService.getGlobal({
|
||||
key: 'email_domain'
|
||||
})) as any) ?? ("mail." + $page.url.hostname)
|
||||
})) as any) ?? null
|
||||
}
|
||||
getEmailDomain()
|
||||
|
||||
@@ -393,26 +393,37 @@ done`
|
||||
{/key}
|
||||
</TabContent>
|
||||
<TabContent value="email">
|
||||
<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>
|
||||
{#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}
|
||||
{/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>
|
||||
<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>
|
||||
|
||||
@@ -46,8 +46,7 @@ export const settings: Record<string, Setting[]> = {
|
||||
},
|
||||
{
|
||||
label: 'Email domain',
|
||||
description:
|
||||
'Domain to display in webhooks for email triggers, default is the webpage domain prefixed by "mail."',
|
||||
description: 'Domain to display in webhooks for email triggers (should match the MX record)',
|
||||
key: 'email_domain',
|
||||
fieldType: 'text',
|
||||
storage: 'setting',
|
||||
|
||||
Reference in New Issue
Block a user