|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
@ -1,114 +0,0 @@ |
|||||
<script> |
|
||||
import { sortBy } from "lodash/fp" |
|
||||
import { automationStore } from "builderStore" |
|
||||
import { ActionButton, Popover, Modal } from "@budibase/bbui" |
|
||||
import { DropdownContainer, DropdownItem } from "components/common/Dropdowns" |
|
||||
import CreateWebhookModal from "../Shared/CreateWebhookModal.svelte" |
|
||||
|
|
||||
$: hasTrigger = $automationStore.selectedAutomation.hasTrigger() |
|
||||
$: tabs = [ |
|
||||
{ |
|
||||
label: "Trigger", |
|
||||
value: "TRIGGER", |
|
||||
icon: "Algorithm", |
|
||||
disabled: hasTrigger, |
|
||||
}, |
|
||||
{ |
|
||||
label: "Internal", |
|
||||
value: "ACTION", |
|
||||
internal: true, |
|
||||
icon: "Actions", |
|
||||
disabled: !hasTrigger, |
|
||||
}, |
|
||||
{ |
|
||||
label: "External", |
|
||||
value: "ACTION", |
|
||||
internal: false, |
|
||||
icon: "Extension", |
|
||||
disabled: !hasTrigger, |
|
||||
}, |
|
||||
] |
|
||||
|
|
||||
let selectedIndex |
|
||||
let anchors = [] |
|
||||
let popover |
|
||||
let webhookModal |
|
||||
$: selectedTab = selectedIndex == null ? null : tabs[selectedIndex].value |
|
||||
$: selectedInternal = |
|
||||
selectedIndex == null ? null : tabs[selectedIndex].internal |
|
||||
$: anchor = selectedIndex === -1 ? null : anchors[selectedIndex] |
|
||||
$: blocks = sortBy(entry => entry[1].name)( |
|
||||
Object.entries($automationStore.blockDefinitions[selectedTab] ?? {}) |
|
||||
).filter( |
|
||||
entry => selectedInternal == null || entry[1].internal === selectedInternal |
|
||||
) |
|
||||
|
|
||||
function onChangeTab(idx) { |
|
||||
selectedIndex = idx |
|
||||
popover.show() |
|
||||
} |
|
||||
|
|
||||
function closePopover() { |
|
||||
selectedIndex = null |
|
||||
popover.hide() |
|
||||
} |
|
||||
|
|
||||
function addBlockToAutomation(stepId, blockDefinition) { |
|
||||
const newBlock = $automationStore.selectedAutomation.constructBlock( |
|
||||
selectedTab, |
|
||||
stepId, |
|
||||
blockDefinition |
|
||||
) |
|
||||
automationStore.actions.addBlockToAutomation(newBlock) |
|
||||
closePopover() |
|
||||
if (stepId === "WEBHOOK") { |
|
||||
webhookModal.show() |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="tab-container"> |
|
||||
{#each tabs as tab, idx} |
|
||||
<div bind:this={anchors[idx]}> |
|
||||
<ActionButton |
|
||||
quiet |
|
||||
size="S" |
|
||||
icon={tab.icon} |
|
||||
disabled={tab.disabled} |
|
||||
on:click={tab.disabled ? null : () => onChangeTab(idx)} |
|
||||
> |
|
||||
{tab.label} |
|
||||
</ActionButton> |
|
||||
</div> |
|
||||
{/each} |
|
||||
</div> |
|
||||
<Popover |
|
||||
on:close={() => (selectedIndex = null)} |
|
||||
bind:this={popover} |
|
||||
{anchor} |
|
||||
align="left" |
|
||||
> |
|
||||
<DropdownContainer> |
|
||||
{#each blocks as [stepId, blockDefinition]} |
|
||||
<DropdownItem |
|
||||
icon={blockDefinition.icon} |
|
||||
title={blockDefinition.name} |
|
||||
subtitle={blockDefinition.description} |
|
||||
on:click={() => addBlockToAutomation(stepId, blockDefinition)} |
|
||||
/> |
|
||||
{/each} |
|
||||
</DropdownContainer> |
|
||||
</Popover> |
|
||||
<Modal bind:this={webhookModal} width="30%"> |
|
||||
<CreateWebhookModal /> |
|
||||
</Modal> |
|
||||
|
|
||||
<style> |
|
||||
.tab-container { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
min-height: 24px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,149 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Layout, Detail, Body, Icon } from "@budibase/bbui" |
||||
|
import { automationStore } from "builderStore" |
||||
|
import DiscordLogo from "assets/discord.svg" |
||||
|
import ZapierLogo from "assets/zapier.png" |
||||
|
import IntegromatLogo from "assets/integromat.png" |
||||
|
import SlackLogo from "assets/slack.svg" |
||||
|
import n8nlogo from "assets/n8n.png" |
||||
|
|
||||
|
import { database } from "stores/backend" |
||||
|
$: instanceId = $database._id |
||||
|
|
||||
|
let selectedAction |
||||
|
let actionVal |
||||
|
export let blockComplete |
||||
|
|
||||
|
let externalActionsPredicate = [ |
||||
|
{ name: "zapier", logo: ZapierLogo }, |
||||
|
{ name: "discord", logo: DiscordLogo }, |
||||
|
{ name: "slack", logo: SlackLogo }, |
||||
|
{ name: "integromat", logo: IntegromatLogo }, |
||||
|
{ name: "n8n", logo: n8nlogo }, |
||||
|
] |
||||
|
let actions = Object.entries($automationStore.blockDefinitions.ACTION) |
||||
|
|
||||
|
const externalActions = actions.reduce((acc, elm) => { |
||||
|
const [k, v] = elm |
||||
|
if (externalActionsPredicate.some(pred => pred.name === k)) { |
||||
|
acc[k] = v |
||||
|
} |
||||
|
return acc |
||||
|
}, {}) |
||||
|
|
||||
|
const internalActions = actions.reduce((acc, elm) => { |
||||
|
const [k, v] = elm |
||||
|
if (!externalActionsPredicate.some(pred => pred.name === k)) { |
||||
|
acc[k] = v |
||||
|
} |
||||
|
return acc |
||||
|
}, {}) |
||||
|
|
||||
|
const selectAction = action => { |
||||
|
actionVal = action |
||||
|
selectedAction = action.name |
||||
|
} |
||||
|
|
||||
|
async function addBlockToAutomation() { |
||||
|
const newBlock = $automationStore.selectedAutomation.constructBlock( |
||||
|
"ACTION", |
||||
|
actionVal.stepId, |
||||
|
actionVal |
||||
|
) |
||||
|
automationStore.actions.addBlockToAutomation(newBlock) |
||||
|
await automationStore.actions.save({ |
||||
|
instanceId, |
||||
|
automation: $automationStore.selectedAutomation?.automation, |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
title="Create Automation" |
||||
|
confirmText="Save" |
||||
|
size="M" |
||||
|
disabled={!selectedAction} |
||||
|
onConfirm={() => { |
||||
|
blockComplete = true |
||||
|
addBlockToAutomation() |
||||
|
}} |
||||
|
> |
||||
|
<Body size="XS">Select an app or event.</Body> |
||||
|
<Layout noPadding> |
||||
|
<Body size="S">Apps</Body> |
||||
|
|
||||
|
<div class="item-list"> |
||||
|
{#each Object.entries(externalActions) as [idx, action]} |
||||
|
<div |
||||
|
class="item" |
||||
|
class:selected={selectedAction === action.name} |
||||
|
on:click={() => selectAction(action)} |
||||
|
> |
||||
|
<div class="item-body"> |
||||
|
<img |
||||
|
width="20" |
||||
|
height="20" |
||||
|
src={externalActionsPredicate.find(val => val.name === idx).logo} |
||||
|
alt="zapier" |
||||
|
/> |
||||
|
<span class="icon-spacing"> |
||||
|
<Body size="XS">{idx.charAt(0).toUpperCase() + idx.slice(1)}</Body |
||||
|
></span |
||||
|
> |
||||
|
</div> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
|
||||
|
<Detail size="S">Actions</Detail> |
||||
|
|
||||
|
<div class="item-list"> |
||||
|
{#each Object.entries(internalActions) as [idx, action]} |
||||
|
<div |
||||
|
class="item" |
||||
|
class:selected={selectedAction === action.name} |
||||
|
on:click={() => selectAction(action)} |
||||
|
> |
||||
|
<div class="item-body"> |
||||
|
<Icon name={action.icon} /> |
||||
|
<span class="icon-spacing"> |
||||
|
<Body size="XS">{action.name}</Body></span |
||||
|
> |
||||
|
</div> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</Layout> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
.icon-spacing { |
||||
|
margin-left: var(--spacing-m); |
||||
|
} |
||||
|
.item-body { |
||||
|
display: flex; |
||||
|
margin-left: var(--spacing-m); |
||||
|
} |
||||
|
.item-list { |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); |
||||
|
grid-gap: var(--spectrum-alias-grid-baseline); |
||||
|
} |
||||
|
|
||||
|
.item { |
||||
|
cursor: pointer; |
||||
|
display: grid; |
||||
|
grid-gap: var(--spectrum-alias-grid-margin-xsmall); |
||||
|
padding: var(--spectrum-alias-item-padding-s); |
||||
|
background: var(--spectrum-alias-background-color-secondary); |
||||
|
transition: 0.3s all; |
||||
|
border: solid #3b3d3c; |
||||
|
border-radius: 5px; |
||||
|
box-sizing: border-box; |
||||
|
border-width: 2px; |
||||
|
} |
||||
|
.item:hover, |
||||
|
.selected { |
||||
|
background: var(--spectrum-alias-background-color-tertiary); |
||||
|
} |
||||
|
</style> |
||||
|
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 353 B |
@ -0,0 +1,13 @@ |
|||||
|
import DiscordLogo from "assets/discord.svg" |
||||
|
import ZapierLogo from "assets/zapier.png" |
||||
|
import IntegromatLogo from "assets/integromat.png" |
||||
|
import SlackLogo from "assets/slack.svg" |
||||
|
import n8nlogo from "assets/n8n.png" |
||||
|
|
||||
|
export const externalActions = { |
||||
|
zapier: { name: "zapier", icon: ZapierLogo }, |
||||
|
discord: { name: "discord", icon: DiscordLogo }, |
||||
|
slack: { name: "slack", icon: SlackLogo }, |
||||
|
integromat: { name: "integromat", icon: IntegromatLogo }, |
||||
|
n8n: { name: "n8n", icon: n8nlogo }, |
||||
|
} |
||||
@ -1,86 +1,200 @@ |
|||||
<script> |
<script> |
||||
import { automationStore } from "builderStore" |
import { automationStore } from "builderStore" |
||||
import AutomationBlockTagline from "./AutomationBlockTagline.svelte" |
import { |
||||
import { Icon } from "@budibase/bbui" |
Icon, |
||||
|
Divider, |
||||
|
Layout, |
||||
|
Body, |
||||
|
Detail, |
||||
|
Modal, |
||||
|
Button, |
||||
|
StatusLight, |
||||
|
} from "@budibase/bbui" |
||||
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte" |
||||
|
import CreateWebhookModal from "components/automation/shared/CreateWebhookModal.svelte" |
||||
|
import ResultsModal from "./ResultsModal.svelte" |
||||
|
import ActionModal from "./ActionModal.svelte" |
||||
|
import { database } from "stores/backend" |
||||
|
import { externalActions } from "./ExternalActions" |
||||
|
|
||||
export let onSelect |
export let onSelect |
||||
export let block |
export let block |
||||
|
export let testDataModal |
||||
let selected |
let selected |
||||
|
let webhookModal |
||||
|
let actionModal |
||||
|
let resultsModal |
||||
|
let setupToggled |
||||
|
let blockComplete |
||||
|
$: testResult = $automationStore.selectedAutomation.testResults?.steps.filter( |
||||
|
step => step.stepId === block.stepId |
||||
|
) |
||||
|
$: instanceId = $database._id |
||||
|
|
||||
|
$: isTrigger = block.type === "TRIGGER" |
||||
|
|
||||
$: selected = $automationStore.selectedBlock?.id === block.id |
$: selected = $automationStore.selectedBlock?.id === block.id |
||||
$: steps = |
$: steps = |
||||
$automationStore.selectedAutomation?.automation?.definition?.steps ?? [] |
$automationStore.selectedAutomation?.automation?.definition?.steps ?? [] |
||||
|
|
||||
$: blockIdx = steps.findIndex(step => step.id === block.id) |
$: blockIdx = steps.findIndex(step => step.id === block.id) |
||||
$: allowDeleteTrigger = !steps.length |
$: lastStep = !isTrigger && blockIdx + 1 === steps.length |
||||
|
|
||||
|
// Logic for hiding / showing the add button.first we check if it has a child |
||||
|
// then we check to see whether its inputs have been commpleted |
||||
|
$: disableAddButton = isTrigger |
||||
|
? $automationStore.selectedAutomation?.automation?.definition?.steps |
||||
|
.length > 0 |
||||
|
: !isTrigger && steps.length - blockIdx > 1 |
||||
|
$: hasCompletedInputs = Object.keys( |
||||
|
block.schema?.inputs?.properties || {} |
||||
|
).every(x => block?.inputs[x]) |
||||
|
|
||||
function deleteStep() { |
async function deleteStep() { |
||||
automationStore.actions.deleteAutomationBlock(block) |
automationStore.actions.deleteAutomationBlock(block) |
||||
|
await automationStore.actions.save({ |
||||
|
instanceId, |
||||
|
automation: $automationStore.selectedAutomation?.automation, |
||||
|
}) |
||||
} |
} |
||||
</script> |
</script> |
||||
|
|
||||
<div |
<div |
||||
class={`block ${block.type} hoverable`} |
class={`block ${block.type} hoverable`} |
||||
class:selected |
class:selected |
||||
on:click={() => onSelect(block)} |
on:click={() => { |
||||
|
onSelect(block) |
||||
|
}} |
||||
> |
> |
||||
<header> |
<div class="blockSection"> |
||||
{#if block.type === "TRIGGER"} |
<div |
||||
<Icon name="Light" /> |
on:click={() => { |
||||
<span>When this happens...</span> |
blockComplete = !blockComplete |
||||
{:else if block.type === "ACTION"} |
}} |
||||
<Icon name="FlashOn" /> |
class="splitHeader" |
||||
<span>Do this...</span> |
> |
||||
{:else if block.type === "LOGIC"} |
<div style="display: flex;"> |
||||
<Icon name="Branch2" /> |
{#if externalActions[block.stepId]} |
||||
<span>Only continue if...</span> |
<img |
||||
{/if} |
alt={externalActions[block.stepId].name} |
||||
<div class="label"> |
width="35px" |
||||
{#if block.type === "TRIGGER"}Trigger{:else}Step {blockIdx + 1}{/if} |
height="35px" |
||||
|
src={externalActions[block.stepId].icon} |
||||
|
/> |
||||
|
{:else} |
||||
|
<svg |
||||
|
width="35px" |
||||
|
height="35px" |
||||
|
class="spectrum-Icon" |
||||
|
style="color:grey;" |
||||
|
focusable="false" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-icon-18-{block.icon}" /> |
||||
|
</svg> |
||||
|
{/if} |
||||
|
<div class="iconAlign"> |
||||
|
<Body size="XS">When this happens:</Body> |
||||
|
|
||||
|
<Detail size="S">{block?.name?.toUpperCase() || ""}</Detail> |
||||
|
</div> |
||||
|
</div> |
||||
|
{#if testResult} |
||||
|
<span on:click={() => resultsModal.show()}> |
||||
|
<StatusLight |
||||
|
positive={isTrigger || testResult[0].outputs?.success} |
||||
|
negative={!testResult[0].outputs?.success} |
||||
|
><Body size="XS">View response</Body></StatusLight |
||||
|
> |
||||
|
</span> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
{#if !blockComplete} |
||||
|
<Divider noMargin /> |
||||
|
<div class="blockSection"> |
||||
|
<Layout noPadding gap="S"> |
||||
|
<div class="splitHeader"> |
||||
|
<div |
||||
|
on:click|stopPropagation={() => { |
||||
|
setupToggled = !setupToggled |
||||
|
}} |
||||
|
class="toggle" |
||||
|
> |
||||
|
{#if setupToggled} |
||||
|
<Icon size="M" name="ChevronDown" /> |
||||
|
{:else} |
||||
|
<Icon size="M" name="ChevronRight" /> |
||||
|
{/if} |
||||
|
<Detail size="S">Setup</Detail> |
||||
|
</div> |
||||
|
{#if !isTrigger} |
||||
|
<div on:click={() => deleteStep()}> |
||||
|
<Icon name="DeleteOutline" /> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
{#if setupToggled} |
||||
|
<AutomationBlockSetup |
||||
|
schemaProperties={Object.entries(block.schema.inputs.properties)} |
||||
|
{block} |
||||
|
{webhookModal} |
||||
|
/> |
||||
|
{#if lastStep} |
||||
|
<Button on:click={() => testDataModal.show()} cta |
||||
|
>Continue and test automation</Button |
||||
|
> |
||||
|
{/if} |
||||
|
<Button |
||||
|
disabled={disableAddButton ? true : !hasCompletedInputs} |
||||
|
on:click={() => { |
||||
|
setupToggled = false |
||||
|
actionModal.show() |
||||
|
}} |
||||
|
primary={!isTrigger} |
||||
|
cta={isTrigger}>Add Action</Button |
||||
|
> |
||||
|
{/if} |
||||
|
</Layout> |
||||
</div> |
</div> |
||||
{#if block.type !== "TRIGGER" || allowDeleteTrigger} |
{/if} |
||||
<div on:click|stopPropagation={deleteStep}><Icon name="Close" /></div> |
|
||||
{/if} |
<Modal bind:this={resultsModal} width="30%"> |
||||
</header> |
<ResultsModal {isTrigger} {testResult} /> |
||||
<hr /> |
</Modal> |
||||
<p> |
|
||||
<AutomationBlockTagline {block} /> |
<Modal bind:this={actionModal} width="30%"> |
||||
</p> |
<ActionModal bind:blockComplete /> |
||||
|
</Modal> |
||||
|
|
||||
|
<Modal bind:this={webhookModal} width="30%"> |
||||
|
<CreateWebhookModal /> |
||||
|
</Modal> |
||||
</div> |
</div> |
||||
|
|
||||
<style> |
<style> |
||||
|
.toggle { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.splitHeader { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
.iconAlign { |
||||
|
padding: 0 0 0 var(--spacing-m); |
||||
|
display: inline-block; |
||||
|
} |
||||
.block { |
.block { |
||||
width: 360px; |
width: 360px; |
||||
padding: 20px; |
|
||||
border-radius: var(--border-radius-m); |
|
||||
transition: 0.3s all ease; |
|
||||
box-shadow: 0 4px 30px 0 rgba(57, 60, 68, 0.08); |
|
||||
font-size: 16px; |
font-size: 16px; |
||||
background-color: var(--spectrum-global-color-gray-50); |
background-color: var(--spectrum-alias-background-color-secondary); |
||||
color: var(--grey-9); |
color: var(--grey-9); |
||||
} |
border: 1px solid var(--spectrum-global-color-gray-300); |
||||
.block.selected, |
border-radius: 4px 4px 4px 4px; |
||||
.block:hover { |
|
||||
transform: scale(1.1); |
|
||||
box-shadow: 0 4px 30px 0 rgba(57, 60, 68, 0.15); |
|
||||
} |
} |
||||
|
|
||||
header { |
.blockSection { |
||||
font-size: 16px; |
padding: var(--spacing-xl); |
||||
font-weight: 600; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
gap: var(--spacing-xs); |
|
||||
} |
|
||||
header span { |
|
||||
flex: 1 1 auto; |
|
||||
} |
|
||||
header .label { |
|
||||
font-size: 14px; |
|
||||
padding: var(--spacing-s); |
|
||||
border-radius: var(--border-radius-m); |
|
||||
background-color: var(--grey-2); |
|
||||
color: var(--grey-8); |
|
||||
} |
} |
||||
</style> |
</style> |
||||
|
|||||
@ -0,0 +1,88 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Icon, Detail, Badge, TextArea } from "@budibase/bbui" |
||||
|
|
||||
|
export let testResult |
||||
|
export let isTrigger |
||||
|
let inputToggled |
||||
|
let outputToggled |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
showCloseIcon={false} |
||||
|
showConfirmButton={false} |
||||
|
title="Test Automation" |
||||
|
cancelText="Close" |
||||
|
> |
||||
|
<div slot="header"> |
||||
|
<div style="float: right;"> |
||||
|
{#if isTrigger || testResult[0].outputs.success} |
||||
|
<Badge green><Icon size="S" name="CheckmarkCircle" /></Badge> |
||||
|
{:else} |
||||
|
<Badge red><Icon size="S" name="CloseCircle" /></Badge> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div |
||||
|
on:click={() => { |
||||
|
inputToggled = !inputToggled |
||||
|
}} |
||||
|
class="toggle splitHeader" |
||||
|
> |
||||
|
<div> |
||||
|
<div style="display: flex; align-items: center;"> |
||||
|
<span style="padding-left: var(--spacing-s);"> |
||||
|
<Detail size="S">Input</Detail> |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div> |
||||
|
{#if inputToggled} |
||||
|
<Icon size="M" name="ChevronDown" /> |
||||
|
{:else} |
||||
|
<Icon size="M" name="ChevronRight" /> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
{#if inputToggled} |
||||
|
<TextArea disabled value={JSON.stringify(testResult[0].inputs, null, 2)} /> |
||||
|
{/if} |
||||
|
|
||||
|
<div |
||||
|
on:click={() => { |
||||
|
outputToggled = !outputToggled |
||||
|
}} |
||||
|
class="toggle splitHeader" |
||||
|
> |
||||
|
<div> |
||||
|
<div style="display: flex; align-items: center;"> |
||||
|
<span style="padding-left: var(--spacing-s);"> |
||||
|
<Detail size="S">Output</Detail> |
||||
|
</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div> |
||||
|
{#if outputToggled} |
||||
|
<Icon size="M" name="ChevronDown" /> |
||||
|
{:else} |
||||
|
<Icon size="M" name="ChevronRight" /> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
{#if outputToggled} |
||||
|
<TextArea disabled value={JSON.stringify(testResult[0].outputs, null, 2)} /> |
||||
|
{/if} |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
.splitHeader { |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
|
||||
|
.toggle { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,70 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Tabs, Tab, TextArea, Label } from "@budibase/bbui" |
||||
|
import { automationStore } from "builderStore" |
||||
|
import AutomationBlockSetup from "../../SetupPanel/AutomationBlockSetup.svelte" |
||||
|
import { cloneDeep } from "lodash/fp" |
||||
|
|
||||
|
let failedParse = null |
||||
|
// clone the trigger so we're not mutating the reference |
||||
|
let trigger = cloneDeep( |
||||
|
$automationStore.selectedAutomation.automation.definition.trigger |
||||
|
) |
||||
|
|
||||
|
if (!$automationStore.selectedAutomation.automation.testData) { |
||||
|
$automationStore.selectedAutomation.automation.testData = {} |
||||
|
} |
||||
|
|
||||
|
// get the outputs so we can define the fields |
||||
|
let schemaProperties = Object.entries(trigger.schema.outputs.properties || {}) |
||||
|
|
||||
|
// check to see if there is existing test data in the store |
||||
|
$: testData = $automationStore.selectedAutomation.automation.testData |
||||
|
|
||||
|
// Checj the schema to see if required fields have been entered |
||||
|
$: isError = !trigger.schema.outputs.required.every( |
||||
|
required => testData[required] |
||||
|
) |
||||
|
|
||||
|
function parseTestJSON(e) { |
||||
|
try { |
||||
|
const obj = JSON.parse(e.detail) |
||||
|
failedParse = null |
||||
|
automationStore.actions.addTestDataToAutomation(obj) |
||||
|
} catch (e) { |
||||
|
failedParse = "Invalid JSON" |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
title="Add test data" |
||||
|
confirmText="Test" |
||||
|
showConfirmButton={true} |
||||
|
disabled={isError} |
||||
|
onConfirm={() => { |
||||
|
automationStore.actions.addTestDataToAutomation(testData) |
||||
|
automationStore.actions.test($automationStore.selectedAutomation, testData) |
||||
|
}} |
||||
|
cancelText="Cancel" |
||||
|
> |
||||
|
<Tabs selected="Form" quiet |
||||
|
><Tab icon="Form" title="Form" |
||||
|
><AutomationBlockSetup |
||||
|
bind:testData |
||||
|
{schemaProperties} |
||||
|
block={trigger} |
||||
|
/></Tab |
||||
|
> |
||||
|
<Tab icon="FileJson" title="JSON"> |
||||
|
<Label>JSON</Label><TextArea |
||||
|
value={JSON.stringify( |
||||
|
$automationStore.selectedAutomation.automation.testData, |
||||
|
null, |
||||
|
2 |
||||
|
)} |
||||
|
error={failedParse} |
||||
|
on:change={e => parseTestJSON(e)} |
||||
|
/> |
||||
|
</Tab> |
||||
|
</Tabs> |
||||
|
</ModalContent> |
||||
@ -1,96 +0,0 @@ |
|||||
<script> |
|
||||
import { automationStore } from "builderStore" |
|
||||
import { database } from "stores/backend" |
|
||||
import { notifications, Button, Modal, Heading, Toggle } from "@budibase/bbui" |
|
||||
import AutomationBlockSetup from "./AutomationBlockSetup.svelte" |
|
||||
import CreateWebookModal from "../Shared/CreateWebhookModal.svelte" |
|
||||
|
|
||||
let webhookModal |
|
||||
|
|
||||
$: instanceId = $database._id |
|
||||
$: automation = $automationStore.selectedAutomation?.automation |
|
||||
$: automationLive = automation?.live |
|
||||
|
|
||||
function setAutomationLive(live) { |
|
||||
if (automationLive === live) { |
|
||||
return |
|
||||
} |
|
||||
automation.live = live |
|
||||
automationStore.actions.save({ instanceId, automation }) |
|
||||
if (live) { |
|
||||
notifications.info(`Automation ${automation.name} enabled.`) |
|
||||
} else { |
|
||||
notifications.error(`Automation ${automation.name} disabled.`) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function testAutomation() { |
|
||||
const result = await automationStore.actions.test({ |
|
||||
automation: $automationStore.selectedAutomation.automation, |
|
||||
}) |
|
||||
if (result.status === 200) { |
|
||||
notifications.success( |
|
||||
`Automation ${automation.name} triggered successfully.` |
|
||||
) |
|
||||
} else { |
|
||||
notifications.error(`Failed to trigger automation ${automation.name}.`) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
async function saveAutomation() { |
|
||||
await automationStore.actions.save({ |
|
||||
instanceId, |
|
||||
automation, |
|
||||
}) |
|
||||
notifications.success(`Automation ${automation.name} saved.`) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="title"> |
|
||||
<Heading size="S">Setup</Heading> |
|
||||
<Toggle |
|
||||
value={automationLive} |
|
||||
on:change={() => setAutomationLive(!automationLive)} |
|
||||
dataCy="activate-automation" |
|
||||
text="Live" |
|
||||
/> |
|
||||
</div> |
|
||||
{#if $automationStore.selectedBlock} |
|
||||
<AutomationBlockSetup |
|
||||
bind:block={$automationStore.selectedBlock} |
|
||||
{webhookModal} |
|
||||
/> |
|
||||
{:else if automation} |
|
||||
<div class="block-label">{automation.name}</div> |
|
||||
<Button secondary on:click={testAutomation}>Test Automation</Button> |
|
||||
{/if} |
|
||||
<Button |
|
||||
secondary |
|
||||
wide |
|
||||
data-cy="save-automation-setup" |
|
||||
on:click={saveAutomation} |
|
||||
> |
|
||||
Save Automation |
|
||||
</Button> |
|
||||
<Modal bind:this={webhookModal} width="30%"> |
|
||||
<CreateWebookModal /> |
|
||||
</Modal> |
|
||||
|
|
||||
<style> |
|
||||
.title { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
gap: var(--spacing-xs); |
|
||||
} |
|
||||
.title :global(h1) { |
|
||||
flex: 1 1 auto; |
|
||||
} |
|
||||
|
|
||||
.block-label { |
|
||||
font-size: var(--spectrum-global-dimension-font-size-75); |
|
||||
font-weight: 600; |
|
||||
color: var(--grey-7); |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,8 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
const updateDotEnv = require("update-dotenv") |
||||
|
|
||||
|
const arg = process.argv.slice(2)[0] |
||||
|
|
||||
|
updateDotEnv({ |
||||
|
SELF_HOSTED: arg === "enable" ? "1" : "0", |
||||
|
}).then(() => console.log("Updated server!")) |
||||
@ -0,0 +1,8 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
const updateDotEnv = require("update-dotenv") |
||||
|
|
||||
|
const arg = process.argv.slice(2)[0] |
||||
|
|
||||
|
updateDotEnv({ |
||||
|
SELF_HOSTED: arg === "enable" ? "1" : "0", |
||||
|
}).then(() => console.log("Updated worker!")) |
||||
@ -0,0 +1,8 @@ |
|||||
|
const Router = require("@koa/router") |
||||
|
const controller = require("../../controllers/system/environment") |
||||
|
|
||||
|
const router = Router() |
||||
|
|
||||
|
router.get("/api/system/environment", controller.fetch) |
||||
|
|
||||
|
module.exports = router |
||||
@ -1,8 +0,0 @@ |
|||||
const Router = require("@koa/router") |
|
||||
const controller = require("../../controllers/system/flags") |
|
||||
|
|
||||
const router = Router() |
|
||||
|
|
||||
router.get("/api/system/flags", controller.fetch) |
|
||||
|
|
||||
module.exports = router |
|
||||