mirror of https://github.com/Budibase/budibase.git
75 changed files with 1020 additions and 420 deletions
@ -1,85 +0,0 @@ |
|||
# This workflow will build and push a new container image to Amazon ECR, |
|||
# and then will deploy a new task definition to Amazon ECS, when a release is created |
|||
# |
|||
# To use this workflow, you will need to complete the following set-up steps: |
|||
# |
|||
# 1. Create an ECR repository to store your images. |
|||
# For example: `aws ecr create-repository --repository-name my-ecr-repo --region us-east-2`. |
|||
# Replace the value of `ECR_REPOSITORY` in the workflow below with your repository's name. |
|||
# Replace the value of `aws-region` in the workflow below with your repository's region. |
|||
# |
|||
# 2. Create an ECS task definition, an ECS cluster, and an ECS service. |
|||
# For example, follow the Getting Started guide on the ECS console: |
|||
# https://us-east-2.console.aws.amazon.com/ecs/home?region=us-east-2#/firstRun |
|||
# Replace the values for `service` and `cluster` in the workflow below with your service and cluster names. |
|||
# |
|||
# 3. Store your ECS task definition as a JSON file in your repository. |
|||
# The format should follow the output of `aws ecs register-task-definition --generate-cli-skeleton`. |
|||
# Replace the value of `task-definition` in the workflow below with your JSON file's name. |
|||
# Replace the value of `container-name` in the workflow below with the name of the container |
|||
# in the `containerDefinitions` section of the task definition. |
|||
# |
|||
# 4. Store an IAM user access key in GitHub Actions secrets named `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`. |
|||
# See the documentation for each action used below for the recommended IAM policies for this IAM user, |
|||
# and best practices on handling the access key credentials. |
|||
|
|||
on: |
|||
push: |
|||
tags: |
|||
- 'v*' |
|||
|
|||
name: Deploy to Amazon ECS |
|||
|
|||
jobs: |
|||
deploy: |
|||
name: deploy |
|||
runs-on: ubuntu-16.04 |
|||
|
|||
steps: |
|||
- name: Checkout |
|||
uses: actions/checkout@v2 |
|||
|
|||
- name: Configure AWS credentials |
|||
uses: aws-actions/configure-aws-credentials@v1 |
|||
with: |
|||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} |
|||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} |
|||
aws-region: eu-west-1 |
|||
|
|||
- name: Download task definition |
|||
run: | |
|||
aws ecs describe-task-definition --task-definition ProdAppServerStackprodbudiapplbfargateserviceprodbudiappserverfargatetaskdefinition2EF7F1E7 --query taskDefinition > task-definition.json |
|||
|
|||
- name: Login to Amazon ECR |
|||
id: login-ecr |
|||
uses: aws-actions/amazon-ecr-login@v1 |
|||
|
|||
- name: Build, tag, and push image to Amazon ECR |
|||
id: build-image |
|||
env: |
|||
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} |
|||
ECR_REPOSITORY: prod-budi-app-server |
|||
IMAGE_TAG: ${{ github.sha }} |
|||
run: | |
|||
# Build a docker container and |
|||
# push it to ECR so that it can |
|||
# be deployed to ECS |
|||
cd packages/server |
|||
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . |
|||
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG |
|||
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" |
|||
- name: Fill in the new image ID in the Amazon ECS task definition |
|||
id: task-def |
|||
uses: aws-actions/amazon-ecs-render-task-definition@v1 |
|||
with: |
|||
task-definition: task-definition.json |
|||
container-name: prod-budi-app-server |
|||
image: ${{ steps.build-image.outputs.image }} |
|||
|
|||
- name: Deploy Amazon ECS task definition |
|||
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 |
|||
with: |
|||
task-definition: ${{ steps.task-def.outputs.task-definition }} |
|||
service: prod-budi-app-server-service |
|||
cluster: prod-budi-app-server |
|||
wait-for-service-stability: true |
|||
@ -0,0 +1,35 @@ |
|||
{{- if .Values.ingress.aws }} |
|||
apiVersion: networking.k8s.io/v1 |
|||
kind: Ingress |
|||
metadata: |
|||
name: ingress-budibase |
|||
annotations: |
|||
kubernetes.io/ingress.class: alb |
|||
alb.ingress.kubernetes.io/scheme: internet-facing |
|||
alb.ingress.kubernetes.io/target-type: ip |
|||
{{- if .Values.ingress.certificateArn }} |
|||
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}' |
|||
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]' |
|||
alb.ingress.kubernetes.io/certificate-arn: {{ .Values.ingress.certificateArn }} |
|||
{{- end }} |
|||
spec: |
|||
rules: |
|||
- http: |
|||
paths: |
|||
{{- if .Values.ingress.certificateArn }} |
|||
- path: / |
|||
pathType: Prefix |
|||
backend: |
|||
service: |
|||
name: ssl-redirect |
|||
port: |
|||
name: use-annotation |
|||
{{- end }} |
|||
- path: / |
|||
pathType: Prefix |
|||
backend: |
|||
service: |
|||
name: proxy-service |
|||
port: |
|||
number: {{ .Values.services.proxy.port }} |
|||
{{- end }} |
|||
@ -1,12 +1,23 @@ |
|||
#!/bin/bash |
|||
|
|||
tag=$1 |
|||
tag=${tag:-latest} |
|||
production=$2 |
|||
|
|||
echo "Tagging images with SHA: $GITHUB_SHA and tag: $tag" |
|||
if [[ ! "$tag" ]]; then |
|||
echo "No tag present. You must pass a tag to this script" |
|||
exit 1 |
|||
fi |
|||
|
|||
echo "Tagging images with tag: $tag" |
|||
|
|||
docker tag app-service budibase/apps:$tag |
|||
docker tag worker-service budibase/worker:$tag |
|||
|
|||
docker push budibase/apps:$tag |
|||
docker push budibase/worker:$tag |
|||
if [[ "$production" ]]; then |
|||
echo "Production Deployment. Tagging latest.." |
|||
docker tag app-service budibase/apps:latest |
|||
docker tag worker-service budibase/worker:latest |
|||
fi |
|||
|
|||
docker push --all-tags budibase/apps |
|||
docker push --all-tags budibase/worker |
|||
|
|||
@ -0,0 +1,172 @@ |
|||
<script> |
|||
import "@spectrum-css/textfield/dist/index-vars.css" |
|||
import "@spectrum-css/actionbutton/dist/index-vars.css" |
|||
import "@spectrum-css/stepper/dist/index-vars.css" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let value = null |
|||
export let placeholder = null |
|||
export let disabled = false |
|||
export let error = null |
|||
export let id = null |
|||
export let readonly = false |
|||
export let updateOnChange = true |
|||
export let quiet = false |
|||
export let min |
|||
export let max |
|||
export let step |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
let focus = false |
|||
|
|||
// We need to keep the field value bound to a different variable in order |
|||
// to properly handle erroneous values. If we don't do this then it is |
|||
// possible for the field to show stale text which does not represent the |
|||
// real value. The reactive statement is to ensure that external changes to |
|||
// the value prop are reflected. |
|||
let fieldValue = value |
|||
$: fieldValue = value |
|||
|
|||
// Ensure step is always a numeric value defaulting to 1 |
|||
$: step = step == null || isNaN(step) ? 1 : step |
|||
|
|||
const updateValue = value => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
const float = parseFloat(value) |
|||
value = isNaN(float) ? null : float |
|||
if (value != null) { |
|||
if (min != null && value < min) { |
|||
value = min |
|||
} else if (max != null && value > max) { |
|||
value = max |
|||
} |
|||
} |
|||
dispatch("change", value) |
|||
fieldValue = value |
|||
} |
|||
|
|||
const onFocus = () => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
focus = true |
|||
} |
|||
|
|||
const onBlur = event => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
focus = false |
|||
updateValue(event.target.value) |
|||
} |
|||
|
|||
const onInput = event => { |
|||
if (readonly || !updateOnChange) { |
|||
return |
|||
} |
|||
updateValue(event.target.value) |
|||
} |
|||
|
|||
const updateValueOnEnter = event => { |
|||
if (readonly) { |
|||
return |
|||
} |
|||
if (event.key === "Enter") { |
|||
updateValue(event.target.value) |
|||
} |
|||
} |
|||
|
|||
const stepUp = () => { |
|||
if (value == null || isNaN(value)) { |
|||
updateValue(step) |
|||
} else { |
|||
updateValue(value + step) |
|||
} |
|||
} |
|||
|
|||
const stepDown = () => { |
|||
if (value == null || isNaN(value)) { |
|||
updateValue(step) |
|||
} else { |
|||
updateValue(value - step) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div |
|||
class="spectrum-Stepper" |
|||
class:spectrum-Stepper--quiet={quiet} |
|||
class:is-invalid={!!error} |
|||
class:is-disabled={disabled} |
|||
class:is-focused={focus} |
|||
> |
|||
{#if error} |
|||
<svg |
|||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-icon-18-Alert" /> |
|||
</svg> |
|||
{/if} |
|||
|
|||
<div class="spectrum-Textfield spectrum-Stepper-textfield"> |
|||
<input |
|||
{disabled} |
|||
{readonly} |
|||
{id} |
|||
bind:value={fieldValue} |
|||
placeholder={placeholder || ""} |
|||
type="number" |
|||
class="spectrum-Textfield-input spectrum-Stepper-input" |
|||
on:click |
|||
on:blur |
|||
on:focus |
|||
on:input |
|||
on:keyup |
|||
on:blur={onBlur} |
|||
on:focus={onFocus} |
|||
on:input={onInput} |
|||
on:keyup={updateValueOnEnter} |
|||
/> |
|||
</div> |
|||
<span class="spectrum-Stepper-buttons"> |
|||
<button |
|||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepUp" |
|||
tabindex="-1" |
|||
on:click={stepUp} |
|||
> |
|||
<svg |
|||
class="spectrum-Icon spectrum-UIIcon-ChevronUp75" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-css-icon-Chevron75" /> |
|||
</svg> |
|||
</button> |
|||
<button |
|||
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepDown" |
|||
tabindex="-1" |
|||
on:click={stepDown} |
|||
> |
|||
<svg |
|||
class="spectrum-Icon spectrum-UIIcon-ChevronDown75" |
|||
focusable="false" |
|||
aria-hidden="true" |
|||
> |
|||
<use xlink:href="#spectrum-css-icon-Chevron75" /> |
|||
</svg> |
|||
</button> |
|||
</span> |
|||
</div> |
|||
|
|||
<style> |
|||
.spectrum-Stepper { |
|||
width: 100%; |
|||
} |
|||
.spectrum-Stepper::before { |
|||
display: none; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,45 @@ |
|||
<script> |
|||
import Field from "./Field.svelte" |
|||
import Stepper from "./Core/Stepper.svelte" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
export let value = null |
|||
export let label = null |
|||
export let labelPosition = "above" |
|||
export let placeholder = null |
|||
export let disabled = false |
|||
export let readonly = false |
|||
export let error = null |
|||
export let updateOnChange = true |
|||
export let quiet = false |
|||
export let min = null |
|||
export let max = null |
|||
export let step = 1 |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
const onChange = e => { |
|||
value = e.detail |
|||
dispatch("change", e.detail) |
|||
} |
|||
</script> |
|||
|
|||
<Field {label} {labelPosition} {error}> |
|||
<Stepper |
|||
{updateOnChange} |
|||
{error} |
|||
{disabled} |
|||
{readonly} |
|||
{value} |
|||
{placeholder} |
|||
{quiet} |
|||
{min} |
|||
{max} |
|||
{step} |
|||
on:change={onChange} |
|||
on:click |
|||
on:input |
|||
on:blur |
|||
on:focus |
|||
on:keyup |
|||
/> |
|||
</Field> |
|||
@ -0,0 +1,68 @@ |
|||
<script> |
|||
import { Select, Label, Stepper } from "@budibase/bbui" |
|||
import { currentAsset, store } from "builderStore" |
|||
import { getActionProviderComponents } from "builderStore/dataBinding" |
|||
import { onMount } from "svelte" |
|||
|
|||
export let parameters |
|||
|
|||
$: actionProviders = getActionProviderComponents( |
|||
$currentAsset, |
|||
$store.selectedComponentId, |
|||
"ChangeFormStep" |
|||
) |
|||
|
|||
const typeOptions = [ |
|||
{ |
|||
label: "Next step", |
|||
value: "next", |
|||
}, |
|||
{ |
|||
label: "Previous step", |
|||
value: "prev", |
|||
}, |
|||
{ |
|||
label: "First step", |
|||
value: "first", |
|||
}, |
|||
{ |
|||
label: "Specific step", |
|||
value: "specific", |
|||
}, |
|||
] |
|||
|
|||
onMount(() => { |
|||
if (!parameters.type) { |
|||
parameters.type = "next" |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<Label small>Form</Label> |
|||
<Select |
|||
placeholder={null} |
|||
bind:value={parameters.componentId} |
|||
options={actionProviders} |
|||
getOptionLabel={x => x._instanceName} |
|||
getOptionValue={x => x._id} |
|||
/> |
|||
<Label small>Step</Label> |
|||
<Select bind:value={parameters.type} options={typeOptions} /> |
|||
{#if parameters.type === "specific"} |
|||
<Label small>Number</Label> |
|||
<Stepper bind:value={parameters.number} /> |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: grid; |
|||
column-gap: var(--spacing-l); |
|||
row-gap: var(--spacing-s); |
|||
grid-template-columns: 60px 1fr; |
|||
align-items: center; |
|||
max-width: 400px; |
|||
margin: 0 auto; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,62 @@ |
|||
/** |
|||
* Finds a component instance by ID |
|||
*/ |
|||
export const findComponentById = (component, componentId) => { |
|||
if (!component || !componentId) { |
|||
return null |
|||
} |
|||
if (component._id === componentId) { |
|||
return component |
|||
} |
|||
if (!component._children?.length) { |
|||
return null |
|||
} |
|||
for (let child of component._children) { |
|||
const result = findComponentById(child, componentId) |
|||
if (result) { |
|||
return result |
|||
} |
|||
} |
|||
return null |
|||
} |
|||
|
|||
/** |
|||
* Finds the component path to a component |
|||
*/ |
|||
export const findComponentPathById = (component, componentId, path = []) => { |
|||
if (!component || !componentId) { |
|||
return null |
|||
} |
|||
path = [...path, component] |
|||
if (component._id === componentId) { |
|||
return path |
|||
} |
|||
if (!component._children?.length) { |
|||
return null |
|||
} |
|||
for (let child of component._children) { |
|||
const result = findComponentPathById(child, componentId, path) |
|||
if (result) { |
|||
return result |
|||
} |
|||
} |
|||
return null |
|||
} |
|||
|
|||
/** |
|||
* Finds all children instances of a certain component type of a given component |
|||
*/ |
|||
export const findChildrenByType = (component, type, children = []) => { |
|||
if (!component) { |
|||
return |
|||
} |
|||
if (component._component.endsWith(`/${type}`)) { |
|||
children.push(component) |
|||
} |
|||
if (!component._children?.length) { |
|||
return |
|||
} |
|||
component._children.forEach(child => { |
|||
findChildrenByType(child, type, children) |
|||
}) |
|||
} |
|||
@ -0,0 +1,35 @@ |
|||
<script> |
|||
import { getContext, setContext } from "svelte" |
|||
import Placeholder from "../Placeholder.svelte" |
|||
|
|||
export let step = 1 |
|||
|
|||
const { styleable, builderStore } = getContext("sdk") |
|||
const component = getContext("component") |
|||
const formContext = getContext("form") |
|||
|
|||
// Set form step context so fields know what step they are within |
|||
setContext("form-step", step || 1) |
|||
|
|||
$: formState = formContext?.formState |
|||
$: currentStep = $formState?.currentStep |
|||
|
|||
// If in the builder preview, show this step if a child is selected |
|||
$: { |
|||
if ( |
|||
formContext && |
|||
$builderStore.inBuilder && |
|||
$builderStore.selectedComponentPath?.includes($component.id) |
|||
) { |
|||
formContext.formApi.setStep(step) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
{#if !formContext} |
|||
<Placeholder text="Form steps need to be wrapped in a form" /> |
|||
{:else if step === currentStep} |
|||
<div use:styleable={$component.styles}> |
|||
<slot /> |
|||
</div> |
|||
{/if} |
|||
Loading…
Reference in new issue