feat: support to set linked secret variable to any field of a newly created resource

This commit is contained in:
Ruben Fiszel
2023-08-27 11:03:49 +02:00
parent a478272a9b
commit fe1e419fa8
11 changed files with 109 additions and 39 deletions
@@ -10,8 +10,9 @@
export let resource_type: string
export let args: Record<string, any> | any = {}
export let password: string
export let linkedSecret: string | undefined = undefined
export let isValid = true
export let linkedSecretCandidates: string[] | undefined = undefined
let schema = emptySchema()
let notFound = false
@@ -100,5 +101,5 @@
>{:else}<div class="py-2" />{/if}
<SimpleEditor autoHeight lang="json" bind:code={rawCode} fixedOverflowWidgets={false} />
{:else}
<SchemaForm noDelete {password} isValid {schema} bind:args />
<SchemaForm noDelete {linkedSecretCandidates} bind:linkedSecret isValid {schema} bind:args />
{/if}
+52 -14
View File
@@ -1,5 +1,8 @@
<script lang="ts" context="module">
const apiTokenApps: Record<string, { img?: string; instructions: string[]; key?: string }> = {
const apiTokenApps: Record<
string,
{ img?: string; instructions: string[]; linkedSecret?: string }
> = {
airtable: {
img: '/airtable_connect.png',
instructions: [
@@ -12,7 +15,7 @@
discord_webhook: {
img: '/discord_webhook.png',
instructions: ['Click on Server Settings', 'Click on Integration', 'Find "Webhooks"'],
key: 'webhook_url'
linkedSecret: 'webhook_url'
},
toggl: {
img: '/toggl_connect.png',
@@ -83,11 +86,39 @@
| undefined = undefined
let args: any = {}
$: key =
apiTokenApps[resource_type]?.key ??
(args != undefined
? Object.keys(args).filter((x) => ['token', 'password', 'api_key', 'key'].includes(x))[0]
: undefined)
$: linkedSecretCandidates = apiTokenApps[resource_type]?.linkedSecret
? ([apiTokenApps[resource_type]?.linkedSecret] as string[])
: args != undefined
? Object.keys(args).filter((x) =>
['token', 'secret', 'key', 'pass', 'private'].some((y) => x.toLowerCase().includes(y))
)
: undefined
function linkedSecretValue(x: string) {
let r = 0
if (x.includes('secret')) {
r += 10
}
if (x.includes('password')) {
r += 5
}
if (x.includes('private')) {
r += 4
}
if (x.includes('key')) {
r += 3
}
if (x.includes('token')) {
r += 2
}
if (x.includes('pass')) {
r += 1
}
return r
}
$: linkedSecret = linkedSecretCandidates?.sort(
(ua, ub) => linkedSecretValue(ua) - linkedSecretValue(ub)
)?.[0]
let scopes: string[] = []
let extra_params: [string, string][] = []
@@ -159,7 +190,7 @@
apiTokenApps[x] ?? {
instructions: '',
img: undefined,
key: undefined
linkedSecret: undefined
}
])
}
@@ -214,12 +245,12 @@
const resourceValue = args
if (!manual || key != undefined) {
if (!manual || linkedSecret != undefined) {
await VariableService.createVariable({
workspace: $workspaceStore!,
requestBody: {
path,
value: manual ? args[key ?? ''] : value,
value: manual ? args[linkedSecret ?? ''] : value,
is_secret: true,
description: emptyString(description)
? `${manual ? 'Token' : 'OAuth token'} for ${resource_type}`
@@ -228,7 +259,7 @@
account: account
}
})
resourceValue[key ?? 'token'] = `$var:${path}`
resourceValue[linkedSecret ?? 'token'] = `$var:${path}`
}
await ResourceService.createResource({
@@ -271,7 +302,7 @@
args['password'] == '' &&
args['api_key'] == '' &&
args['key'] == '' &&
key != undefined) ||
linkedSecret != undefined) ||
(step == 3 && pathError != '') ||
!isValid
@@ -450,7 +481,13 @@
<h2 class="mt-4">Value</h2>
<div class="mt-4">
{#key resource_type}
<ApiConnectForm password={key ?? ''} {resource_type} bind:args bind:isValid />
<ApiConnectForm
{linkedSecret}
{linkedSecretCandidates}
{resource_type}
bind:args
bind:isValid
/>
{/key}
</div>
@@ -467,7 +504,8 @@
{#if apiTokenApps[resource_type] || !manual}
<ul class="mt-10">
<li>
1. A secret variable containing the {apiTokenApps[resource_type]?.key ?? 'token'}
1. A secret variable containing the {apiTokenApps[resource_type]?.linkedSecret ??
'token'}
<span class="font-bold">{truncateRev(value, 5, '*****')}</span>
will be stored a
<span class="font-mono whitespace-nowrap">{path}</span>.
+1 -1
View File
@@ -450,7 +450,7 @@
{/if}
</div>
{#if variableEditor}
<div class="text-sm text-tertiary-inverse">
<div class="text-sm text-tertiary">
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
Linked to variable <button
class="text-blue-500 underline"
@@ -15,7 +15,7 @@
<Button
variant="border"
color="light"
btnClasses="text-tertiary {small ? 'text-xs' : ''} "
btnClasses="text-primary {small ? 'text-xs' : ''} "
on:click={() => (open = !open)}
>
{text}&nbsp;<Icon data={open ? faChevronUp : faChevronDown} scale={0.5} />
@@ -103,7 +103,7 @@
>
{#if stepDetail == undefined}
<div>
<p class="font-medium text-tertiary text-center pt-4 pb-8">
<p class="font-medium text-secondary text-center pt-4 pb-8">
Click on a step to see its details
</p>
<h3 class="mb-2 font-semibold">Flow Inputs</h3>
@@ -112,7 +112,7 @@
{:else if stepDetail == 'Input'}
<SchemaViewer schema={flow?.schema} />
{:else if stepDetail == 'Result'}
<p class="font-medium text-tertiary text-center pt-4 pb-8"> End of the flow </p>
<p class="font-medium text-secondary text-center pt-4 pb-8"> End of the flow </p>
{:else if typeof stepDetail != 'string' && stepDetail.value}
<div class="">
<div class="sticky top-0 bg-surface w-full flex items-center py-2">
@@ -154,7 +154,7 @@
{/if}
</div>
{#if stepDetail.value.type == 'identity'}
<p class="font-medium text-tertiary text-center pt-4 pb-8">
<p class="font-medium text-secondary text-center pt-4 pb-8">
An identity step returns its inputs as outputs
</p>
{:else if stepDetail.value.type == 'rawscript'}
@@ -204,7 +204,7 @@
{/if}
{:else if stepDetail.value.type == 'forloopflow'}
<div>
<p class="font-medium text-tertiary pb-2"> Iterator expression: </p>
<p class="font-medium text-secondary pb-2"> Iterator expression: </p>
{#if stepDetail.value.iterator.type == 'static'}
<ObjectViewer json={stepDetail.value.iterator.value} />
{:else}
@@ -214,18 +214,18 @@
{/if}
</div>
{:else if stepDetail.value.type == 'branchall'}
<p class="font-medium text-tertiary text-center pt-4 pb-8">
<p class="font-medium text-secondary text-center pt-4 pb-8">
All branches will run, regardless of the inputs
</p>
{:else if stepDetail.value.type == 'branchone'}
<p class="font-medium text-tertiary text-center pt-4 pb-8">
<p class="font-medium text-secondary text-center pt-4 pb-8">
Only one branch will run based on a predicate
</p>
{:else if stepDetail.value.type == 'flow'}
<FlowPathViewer noSide path={stepDetail.value.path} />
{/if}
{:else}
<p class="font-medium text-tertiary text-center pt-4 pb-8">
<p class="font-medium text-secondary text-center pt-4 pb-8">
Step {stepDetail} selected
</p>
{/if}
@@ -23,7 +23,7 @@
</Cell>
{/each}
{:else}
<div class="flex flex-col text-xs text-tertiary">
<div class="flex flex-col text-xs text-secondary">
{#each Object.entries(usage) as [k, v]}
<div>
{k}: {v}
+1 -1
View File
@@ -40,7 +40,7 @@
>
</div>
<input
class="block w-full py-2 px-2 {red ? '!border-red-500' : ''} text-sm js-password h-12"
class="block w-full px-2 py-1 {red ? '!border-red-500' : ''} text-sm js-password h-9"
id="password"
type="password"
bind:value={password}
+36 -5
View File
@@ -8,6 +8,8 @@
import { Button } from './common'
import ItemPicker from './ItemPicker.svelte'
import VariableEditor from './VariableEditor.svelte'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
export let schema: Schema | any
export let args: Record<string, any> = {}
@@ -20,7 +22,8 @@
export let shouldHideNoInputs: boolean = false
export let compact = false
export let password: string | undefined = undefined
export let linkedSecret: string | undefined = undefined
export let linkedSecretCandidates: string[] | undefined = undefined
export let noVariablePicker = false
export let flexWrap = false
export let noDelete = false
@@ -99,7 +102,6 @@
disabled={disabledArgs.includes(argName) || disabled}
{editableSchema}
{compact}
password={argName == password}
{variableEditor}
{itemPicker}
bind:pickForField
@@ -125,14 +127,43 @@
properties={schema.properties[argName].properties}
itemsType={schema.properties[argName].items}
disabled={disabledArgs.includes(argName) || disabled}
{editableSchema}
{compact}
password={argName == password}
{variableEditor}
{itemPicker}
bind:pickForField
password={linkedSecret == argName}
extra={schema.properties[argName]}
/>
>
<svelte:fragment slot="actions">
{#if linkedSecretCandidates?.includes(argName)}
<div>
<ToggleButtonGroup
selected={linkedSecret == argName}
on:selected={(e) => {
if (e.detail) {
linkedSecret = argName
} else if (linkedSecret == argName) {
linkedSecret = undefined
}
}}
>
<ToggleButton
value={false}
size="sm"
label="Inlined"
tooltip="The value is inlined in the resource and thus has no special treatment."
/>
<ToggleButton
position="right"
value={true}
size="sm"
label="Secret"
tooltip="The value will be stored in a newly created linked secret variable at the same path. That variable can be permissioned differently, will be treated as a secret the UI, operators will not be able to load it and every access will generate a corresponding audit log."
/>
</ToggleButtonGroup>
</div>{/if}</svelte:fragment
>
</ArgInput>
{/if}
{/if}
</div>
@@ -161,7 +161,7 @@
<div>
<div class="w-full flex justify-between items-center mb-1">
<div class="text-xs text-tertiary font-semibold truncate">
<div class="text-xs text-secondary font-semibold truncate">
Background runnables
<Tooltip
class="mb-0.5"
@@ -26,14 +26,14 @@
<button
class={twMerge(
'border-b-2 py-1 px-4 cursor-pointer transition-all z-10 ease-linear font-medium text-tertiary',
'border-b-2 py-1 px-4 cursor-pointer transition-all z-10 ease-linear font-medium text-primary',
$selected?.startsWith(value)
? 'wm-tab-active'
: 'border-gray-300 dark:border-gray-600 border-opacity-0 hover:border-opacity-100 ',
fontSizeClasses[size],
c,
$selected?.startsWith(value) ? selectedClass : '',
disabled ? 'cursor-not-allowed text-gray-400' : ''
disabled ? 'cursor-not-allowed text-tertiary' : ''
)}
style={`${style} ${$selected?.startsWith(value) ? selectedStyle : ''}`}
on:click={() => {
+4 -4
View File
@@ -9,7 +9,7 @@ const lightTheme = {
textPrimary: '#2d3748',
textSecondary: '#4a5568',
textTertiary: '#718096',
textTertiary: '#505c70',
textDisabled: '#a0aec0',
border: '#ccc',
@@ -27,7 +27,7 @@ const darkTheme = {
textPrimary: '#f3f6f8',
textSecondary: '#e0e7ed',
textTertiary: '#d8dee9',
textTertiary: '#c7ccd6',
textDisabled: '#a0aec0',
border: '#3e4c60',
@@ -478,7 +478,7 @@ const config = {
'--color-text-primary': lightThemeRgb.textPrimary,
'--color-text-secondary': lightThemeRgb.textSecondary,
'--color-text-tetiary': lightThemeRgb.textTertiary,
'--color-text-tertiary': lightThemeRgb.textTertiary,
'--color-text-disabled': lightThemeRgb.textDisabled,
'--color-surface-inverse': darkThemeRgb.surface,
@@ -516,7 +516,7 @@ const config = {
'--color-text-primary': darkThemeRgb.textPrimary,
'--color-text-secondary': darkThemeRgb.textSecondary,
'--color-text-tetiary': darkThemeRgb.textTertiary,
'--color-text-tertiary': darkThemeRgb.textTertiary,
'--color-text-disabled': darkThemeRgb.textDisabled,
'--color-surface-inverse': lightThemeRgb.surface,