Merge branch 'master' of github.com:Budibase/budibase into develop

This commit is contained in:
mike12345567 2022-02-15 16:54:21 +00:00
commit db45086105
40 changed files with 1704 additions and 154 deletions

View File

@ -25,14 +25,17 @@ jobs:
# Pull apps and worker images # Pull apps and worker images
docker pull budibase/apps:$release_tag docker pull budibase/apps:$release_tag
docker pull budibase/worker:$release_tag docker pull budibase/worker:$release_tag
docker pull budibase/proxy:$release_tag
# Tag apps and worker images # Tag apps and worker images
docker tag budibase/apps:$release_tag budibase/apps:$SELFHOST_TAG docker tag budibase/apps:$release_tag budibase/apps:$SELFHOST_TAG
docker tag budibase/worker:$release_tag budibase/worker:$SELFHOST_TAG docker tag budibase/worker:$release_tag budibase/worker:$SELFHOST_TAG
docker tag budibase/proxy:$release_tag budibase/proxy:$SELFHOST_TAG
# Push images # Push images
docker push budibase/apps:$SELFHOST_TAG docker push budibase/apps:$SELFHOST_TAG
docker push budibase/worker:$SELFHOST_TAG docker push budibase/worker:$SELFHOST_TAG
docker push budibase/proxy:$SELFHOST_TAG
env: env:
DOCKER_USER: ${{ secrets.DOCKER_USERNAME }} DOCKER_USER: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }} DOCKER_PASSWORD: ${{ secrets.DOCKER_API_KEY }}

152
hosting/envoy.yaml Normal file
View File

@ -0,0 +1,152 @@
static_resources:
listeners:
- name: main_listener
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
stat_prefix: ingress
codec_type: auto
route_config:
name: local_route
virtual_hosts:
- name: local_services
domains: ["*"]
routes:
- match: { prefix: "/app/" }
route:
cluster: app-service
prefix_rewrite: "/"
- match: { path: "/v1/update" }
route:
cluster: watchtower-service
- match: { prefix: "/builder/" }
route:
cluster: app-service
- match: { prefix: "/builder" }
route:
cluster: app-service
- match: { prefix: "/app_" }
route:
cluster: app-service
# special cases for worker admin (deprecated), global and system API
- match: { prefix: "/api/global/" }
route:
cluster: worker-service
- match: { prefix: "/api/admin/" }
route:
cluster: worker-service
- match: { prefix: "/api/system/" }
route:
cluster: worker-service
- match: { path: "/" }
route:
cluster: app-service
# special case for when API requests are made, can just forward, not to minio
- match: { prefix: "/api/" }
route:
cluster: app-service
timeout: 120s
- match: { prefix: "/worker/" }
route:
cluster: worker-service
prefix_rewrite: "/"
- match: { prefix: "/db/" }
route:
cluster: couchdb-service
prefix_rewrite: "/"
# minio is on the default route because this works
# best, minio + AWS SDK doesn't handle path proxy
- match: { prefix: "/" }
route:
cluster: minio-service
http_filters:
- name: envoy.filters.http.router
clusters:
- name: app-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: app-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: app-service
port_value: 4002
- name: minio-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: minio-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: minio-service
port_value: 9000
- name: worker-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: worker-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: worker-service
port_value: 4003
- name: couchdb-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: couchdb-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: couchdb-service
port_value: 5984
- name: watchtower-service
connect_timeout: 0.25s
type: strict_dns
lb_policy: round_robin
load_assignment:
cluster_name: watchtower-service
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: watchtower-service
port_value: 8080

View File

@ -22,7 +22,7 @@ http {
# buffering # buffering
client_body_buffer_size 1K; client_body_buffer_size 1K;
client_header_buffer_size 1k; client_header_buffer_size 1k;
client_max_body_size 1k; client_max_body_size 10M;
ignore_invalid_headers off; ignore_invalid_headers off;
proxy_buffering off; proxy_buffering off;
@ -36,20 +36,28 @@ http {
server { server {
listen 10000 default_server; listen 10000 default_server;
listen [::]:10000 default_server;
server_name _; server_name _;
port_in_redirect off;
# Security Headers # Security Headers
add_header X-Frame-Options SAMEORIGIN always; add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always;
location /app { location /app {
proxy_pass http://app-service:4002; proxy_pass http://app-service.budibase.svc.cluster.local:4002;
rewrite ^/app/(.*)$ /$1 break; rewrite ^/app/(.*)$ /$1 break;
} }
location = / { location = / {
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://app-service.budibase.svc.cluster.local:4002; proxy_pass http://app-service.budibase.svc.cluster.local:4002;
} }
@ -63,16 +71,22 @@ http {
proxy_pass http://app-service.budibase.svc.cluster.local:4002; proxy_pass http://app-service.budibase.svc.cluster.local:4002;
} }
location ^/(builder|app_) { location ~ ^/(builder|app_) {
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://app-service.budibase.svc.cluster.local:4002; proxy_pass http://app-service.budibase.svc.cluster.local:4002;
} }
location ~ ^/api/(system|admin|global)/ { location ~ ^/api/(system|admin|global)/ {
proxy_pass http://worker-service.budibase.svc.cluster.local:4003; proxy_pass http://worker-service.budibase.svc.cluster.local:4001;
} }
location /worker/ { location /worker/ {
proxy_pass http://worker-service.budibase.svc.cluster.local:4003; proxy_pass http://worker-service.budibase.svc.cluster.local:4001;
rewrite ^/worker/(.*)$ /$1 break; rewrite ^/worker/(.*)$ /$1 break;
} }
@ -105,11 +119,11 @@ http {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_set_header Connection "";
proxy_http_version 1.1;
chunked_transfer_encoding off;
proxy_connect_timeout 300; proxy_connect_timeout 300;
proxy_http_version 1.1;
proxy_set_header Connection "";
chunked_transfer_encoding off;
proxy_pass http://minio-service.budibase.svc.cluster.local:9000; proxy_pass http://minio-service.budibase.svc.cluster.local:9000;
} }

View File

@ -24,7 +24,6 @@ http {
client_header_buffer_size 1k; client_header_buffer_size 1k;
client_max_body_size 1k; client_max_body_size 1k;
ignore_invalid_headers off; ignore_invalid_headers off;
proxy_buffering off;
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' log_format main '$remote_addr - $remote_user [$time_local] "$request" '
@ -37,16 +36,18 @@ http {
server { server {
listen 10000 default_server; listen 10000 default_server;
listen [::]:10000 default_server;
server_name _; server_name _;
client_max_body_size 1000m; client_max_body_size 1000m;
ignore_invalid_headers off; ignore_invalid_headers off;
proxy_buffering off; proxy_buffering off;
port_in_redirect off;
# Security Headers # Security Headers
add_header X-Frame-Options SAMEORIGIN always; add_header X-Frame-Options SAMEORIGIN always;
add_header X-Content-Type-Options nosniff always; add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always; add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'unsafe-inline' 'unsafe-eval' 'self' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always; add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.budi.live https://js.intercomcdn.com https://widget.intercom.io; style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://fonts.googleapis.com https://rsms.me; object-src 'none'; base-uri 'self'; connect-src 'self' https://api-iam.intercom.io https://app.posthog.com wss://nexus-websocket-a.intercom.io; font-src 'self' data: https://cdn.jsdelivr.net https://fonts.gstatic.com https://rsms.me; frame-src 'self'; img-src http: https: data:; manifest-src 'self'; media-src 'self'; worker-src 'none';" always;
location /app { location /app {
proxy_pass http://app-service:4002; proxy_pass http://app-service:4002;
@ -54,6 +55,7 @@ http {
} }
location = / { location = / {
port_in_redirect off;
proxy_pass http://app-service:4002; proxy_pass http://app-service:4002;
} }
@ -62,6 +64,7 @@ http {
} }
location /builder/ { location /builder/ {
port_in_redirect off;
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade; proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade; proxy_set_header Upgrade $http_upgrade;
@ -71,7 +74,14 @@ http {
proxy_pass http://app-service:4002; proxy_pass http://app-service:4002;
} }
location ^/(builder|app_) { location ~ ^/(builder|app_) {
port_in_redirect off;
proxy_http_version 1.1;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://app-service:4002; proxy_pass http://app-service:4002;
} }

View File

@ -1,5 +1,5 @@
{ {
"version": "1.0.58-alpha.7", "version": "1.0.65",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*" "packages/*"

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/backend-core", "name": "@budibase/backend-core",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Budibase backend core libraries used in server and worker", "description": "Budibase backend core libraries used in server and worker",
"main": "src/index.js", "main": "src/index.js",
"author": "Budibase", "author": "Budibase",

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",

View File

@ -17,7 +17,6 @@
let showTooltip = false let showTooltip = false
</script> </script>
<div class:container={!!tooltip}>
<button <button
class:spectrum-Button--cta={cta} class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary} class:spectrum-Button--primary={primary}
@ -30,7 +29,6 @@
{disabled} {disabled}
on:click|preventDefault on:click|preventDefault
on:mouseover={() => (showTooltip = true)} on:mouseover={() => (showTooltip = true)}
on:focus={() => (showTooltip = true)}
on:mouseleave={() => (showTooltip = false)} on:mouseleave={() => (showTooltip = false)}
> >
{#if icon} {#if icon}
@ -58,21 +56,16 @@
</svg> </svg>
</div> </div>
{/if} {/if}
</button>
{#if showTooltip && tooltip} {#if showTooltip && tooltip}
<div class="position">
<div class="tooltip"> <div class="tooltip">
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} /> <Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
</div> </div>
</div>
{/if} {/if}
</div> </button>
<style> <style>
.container { button {
display: flex; position: relative;
align-items: center;
flex-direction: column;
} }
.spectrum-Button-label { .spectrum-Button-label {
white-space: nowrap; white-space: nowrap;
@ -90,12 +83,8 @@
width: 160px; width: 160px;
text-align: center; text-align: center;
transform: translateX(-50%); transform: translateX(-50%);
top: -5px; left: 50%;
} top: calc(100% - 3px);
.position {
position: relative;
width: 0;
height: 0;
} }
.tooltip-icon { .tooltip-icon {
padding-left: var(--spacing-m); padding-left: var(--spacing-m);

View File

@ -1,6 +1,16 @@
<script> <script>
import "@spectrum-css/buttongroup/dist/index-vars.css" import "@spectrum-css/buttongroup/dist/index-vars.css"
export let vertical = false export let vertical = false
export let gap = ""
$: gapStyle =
gap === "L"
? "var(--spacing-l)"
: gap === "M"
? "var(--spacing-m)"
: gap === "S"
? "var(--spacing-s)"
: null
function group(element) { function group(element) {
const buttons = Array.from(element.getElementsByTagName("button")) const buttons = Array.from(element.getElementsByTagName("button"))
@ -14,6 +24,7 @@
use:group use:group
class="spectrum-ButtonGroup" class="spectrum-ButtonGroup"
class:spectrum-ButtonGroup--vertical={vertical} class:spectrum-ButtonGroup--vertical={vertical}
style={gapStyle ? `gap: ${gapStyle};` : null}
> >
<slot /> <slot />
</div> </div>

View File

@ -44,6 +44,11 @@
{/if} {/if}
<style> <style>
.buttons {
display: flex;
gap: var(--spacing-m);
}
.drawer { .drawer {
position: absolute; position: absolute;
bottom: 0; bottom: 0;

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/builder", "name": "@budibase/builder",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"license": "GPL-3.0", "license": "GPL-3.0",
"private": true, "private": true,
"scripts": { "scripts": {
@ -64,10 +64,10 @@
} }
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.58-alpha.7", "@budibase/bbui": "^1.0.65",
"@budibase/client": "^1.0.58-alpha.7", "@budibase/client": "^1.0.65",
"@budibase/frontend-core": "^1.0.58-alpha.7", "@budibase/frontend-core": "^1.0.65",
"@budibase/string-templates": "^1.0.58-alpha.7", "@budibase/string-templates": "^1.0.65",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",

View File

@ -275,10 +275,7 @@ const getProviderContextBindings = (asset, dataProviders) => {
*/ */
const getUserBindings = () => { const getUserBindings = () => {
let bindings = [] let bindings = []
const { schema } = getSchemaForDatasource(null, { const { schema } = getSchemaForTable(TableNames.USERS)
type: "table",
tableId: TableNames.USERS,
})
const keys = Object.keys(schema).sort() const keys = Object.keys(schema).sort()
const safeUser = makePropSafe("user") const safeUser = makePropSafe("user")
keys.forEach(key => { keys.forEach(key => {
@ -385,9 +382,33 @@ export const getButtonContextBindings = (actions, actionId) => {
} }
/** /**
* Gets a schema for a datasource object. * Gets the schema for a certain table ID.
* The options which can be passed in are:
* formSchema: whether the schema is for a form
* searchableSchema: whether to generate a searchable schema, which may have
* fewer fields than a readable schema
* @param tableId the table ID to get the schema for
* @param options options for generating the schema
* @return {{schema: Object, table: Object}}
*/ */
export const getSchemaForDatasource = (asset, datasource, isForm = false) => { export const getSchemaForTable = (tableId, options) => {
return getSchemaForDatasource(null, { type: "table", tableId }, options)
}
/**
* Gets a schema for a datasource object.
* The options which can be passed in are:
* formSchema: whether the schema is for a form
* searchableSchema: whether to generate a searchable schema, which may have
* fewer fields than a readable schema
* @param asset the current root client app asset (layout or screen). This is
* optional and only needed for "provider" datasource types.
* @param datasource the datasource definition
* @param options options for generating the schema
* @return {{schema: Object, table: Object}}
*/
export const getSchemaForDatasource = (asset, datasource, options) => {
options = options || {}
let schema, table let schema, table
if (datasource) { if (datasource) {
@ -399,7 +420,7 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
if (type === "provider") { if (type === "provider") {
const component = findComponent(asset.props, datasource.providerId) const component = findComponent(asset.props, datasource.providerId)
const source = getDatasourceForProvider(asset, component) const source = getDatasourceForProvider(asset, component)
return getSchemaForDatasource(asset, source, isForm) return getSchemaForDatasource(asset, source, options)
} }
// "query" datasources are those targeting non-plus datasources or // "query" datasources are those targeting non-plus datasources or
@ -448,8 +469,16 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
// Determine the schema from the backing entity if not already determined // Determine the schema from the backing entity if not already determined
if (table && !schema) { if (table && !schema) {
if (type === "view") { if (type === "view") {
// For views, the schema is pulled from the `views` property of the
// table
schema = cloneDeep(table.views?.[datasource.name]?.schema) schema = cloneDeep(table.views?.[datasource.name]?.schema)
} else if (type === "query" && isForm) { } else if (
type === "query" &&
(options.formSchema || options.searchableSchema)
) {
// For queries, if we are generating a schema for a form or a searchable
// schema then we want to use the query parameters rather than the
// query schema
schema = {} schema = {}
const params = table.parameters || [] const params = table.parameters || []
params.forEach(param => { params.forEach(param => {
@ -458,6 +487,7 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
} }
}) })
} else { } else {
// Otherwise we just want the schema of the table
schema = cloneDeep(table.schema) schema = cloneDeep(table.schema)
} }
} }
@ -485,9 +515,31 @@ export const getSchemaForDatasource = (asset, datasource, isForm = false) => {
schema = { ...schema, ...jsonAdditions } schema = { ...schema, ...jsonAdditions }
} }
// Add _id and _rev fields for certain types // Determine if we should add ID and rev to the schema
if (schema && !isForm && ["table", "link"].includes(datasource.type)) { const isInternal = table && !table.sql
const isTable = ["table", "link"].includes(datasource.type)
// ID is part of the readable schema for all tables
// Rev is part of the readable schema for internal tables only
let addId = isTable
let addRev = isTable && isInternal
// Don't add ID or rev for form schemas
if (options.formSchema) {
addId = false
addRev = false
}
// ID is only searchable for internal tables
else if (options.searchableSchema) {
addId = isTable && isInternal
}
// Add schema properties if required
if (addId) {
schema["_id"] = { type: "string" } schema["_id"] = { type: "string" }
}
if (addRev) {
schema["_rev"] = { type: "string" } schema["_rev"] = { type: "string" }
} }

View File

@ -141,7 +141,9 @@ const fieldTypeToComponentMap = {
} }
export function makeDatasourceFormComponents(datasource) { export function makeDatasourceFormComponents(datasource) {
const { schema } = getSchemaForDatasource(null, datasource, true) const { schema } = getSchemaForDatasource(null, datasource, {
formSchema: true,
})
let components = [] let components = []
let fields = Object.keys(schema || {}) let fields = Object.keys(schema || {})
fields.forEach(field => { fields.forEach(field => {

View File

@ -28,8 +28,8 @@
import { debounce } from "lodash" import { debounce } from "lodash"
import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte" import ModalBindableInput from "components/common/bindings/ModalBindableInput.svelte"
import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte" import FilterDrawer from "components/design/PropertiesPanel/PropertyControls/FilterEditor/FilterDrawer.svelte"
// need the client lucene builder to convert to the structure API expects
import { LuceneUtils } from "@budibase/frontend-core" import { LuceneUtils } from "@budibase/frontend-core"
import { getSchemaForTable } from "builderStore/dataBinding"
export let block export let block
export let testData export let testData
@ -46,13 +46,13 @@
block || $automationStore.selectedBlock, block || $automationStore.selectedBlock,
$automationStore.selectedAutomation?.automation?.definition $automationStore.selectedAutomation?.automation?.definition
) )
$: inputData = testData ? testData : block.inputs $: inputData = testData ? testData : block.inputs
$: tableId = inputData ? inputData.tableId : null $: tableId = inputData ? inputData.tableId : null
$: table = tableId $: table = tableId
? $tables.list.find(table => table._id === inputData.tableId) ? $tables.list.find(table => table._id === inputData.tableId)
: { schema: {} } : { schema: {} }
$: schemaFields = table ? Object.values(table.schema) : [] $: schema = getSchemaForTable(tableId, { searchableSchema: true }).schema
$: schemaFields = Object.values(schema || {})
const onChange = debounce(async function (e, key) { const onChange = debounce(async function (e, key) {
try { try {

View File

@ -23,6 +23,10 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let bindings export let bindings
// jsValue/hbsValue are the state of the value that is being built
// within this binding panel - the value should not be updated until
// the binding panel is saved. This is the default value of the
// expression when the binding panel is opened, but shouldn't be updated.
export let value = "" export let value = ""
export let valid export let valid
export let allowJS = false export let allowJS = false
@ -51,8 +55,8 @@
}) })
$: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`) $: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`)
const updateValue = value => { const updateValue = val => {
valid = isValid(readableToRuntimeBinding(bindings, value)) valid = isValid(readableToRuntimeBinding(bindings, val))
if (valid) { if (valid) {
dispatch("change", value) dispatch("change", value)
} }
@ -60,7 +64,7 @@
// Adds a HBS helper to the expression // Adds a HBS helper to the expression
const addHelper = helper => { const addHelper = helper => {
hbsValue = addHBSBinding(value, getCaretPosition(), helper.text) hbsValue = addHBSBinding(hbsValue, getCaretPosition(), helper.text)
updateValue(hbsValue) updateValue(hbsValue)
} }

View File

@ -1,7 +1,10 @@
export function addHBSBinding(value, caretPos, binding) { export function addHBSBinding(value, caretPos, binding) {
binding = typeof binding === "string" ? binding : binding.path binding = typeof binding === "string" ? binding : binding.path
value = value == null ? "" : value value = value == null ? "" : value
if (!value.includes("{{") && !value.includes("}}")) {
const left = caretPos?.start ? value.substring(0, caretPos.start) : ""
const right = caretPos?.end ? value.substring(caretPos.end) : ""
if (!left.includes("{{") || !right.includes("}}")) {
binding = `{{ ${binding} }}` binding = `{{ ${binding} }}`
} }
if (caretPos.start) { if (caretPos.start) {

View File

@ -126,6 +126,7 @@
</Layout> </Layout>
<Layout noPadding> <Layout noPadding>
{#if selectedActionComponent} {#if selectedActionComponent}
{#key selectedAction.id}
<div class="selected-action-container"> <div class="selected-action-container">
<svelte:component <svelte:component
this={selectedActionComponent} this={selectedActionComponent}
@ -133,6 +134,7 @@
bindings={allBindings} bindings={allBindings}
/> />
</div> </div>
{/key}
{/if} {/if}
</Layout> </Layout>
</DrawerContent> </DrawerContent>

View File

@ -4,7 +4,7 @@
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { import {
getContextProviderComponents, getContextProviderComponents,
getSchemaForDatasource, getSchemaForTable,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import SaveFields from "./SaveFields.svelte" import SaveFields from "./SaveFields.svelte"
@ -60,7 +60,7 @@
} }
const getSchemaFields = (asset, tableId) => { const getSchemaFields = (asset, tableId) => {
const { schema } = getSchemaForDatasource(asset, { type: "table", tableId }) const { schema } = getSchemaForTable(tableId)
delete schema._id delete schema._id
delete schema._rev delete schema._rev
return Object.values(schema || {}) return Object.values(schema || {})

View File

@ -4,7 +4,7 @@
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { import {
getContextProviderComponents, getContextProviderComponents,
getSchemaForDatasource, getSchemaForTable,
} from "builderStore/dataBinding" } from "builderStore/dataBinding"
import SaveFields from "./SaveFields.svelte" import SaveFields from "./SaveFields.svelte"
@ -60,7 +60,7 @@
} }
const getSchemaFields = (asset, tableId) => { const getSchemaFields = (asset, tableId) => {
const { schema } = getSchemaForDatasource(asset, { type: "table", tableId }) const { schema } = getSchemaForTable(tableId)
return Object.values(schema || {}) return Object.values(schema || {})
} }

View File

@ -90,7 +90,7 @@
</script> </script>
<DrawerContent> <DrawerContent>
<div class="container"> <div className="container">
<Layout noPadding> <Layout noPadding>
<Body size="S"> <Body size="S">
{#if !filters?.length} {#if !filters?.length}
@ -184,6 +184,7 @@
max-width: 1000px; max-width: 1000px;
margin: 0 auto; margin: 0 auto;
} }
.fields { .fields {
display: grid; display: grid;
column-gap: var(--spacing-l); column-gap: var(--spacing-l);

View File

@ -18,7 +18,9 @@
let tempValue = value || [] let tempValue = value || []
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance) $: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema $: schema = getSchemaForDatasource($currentAsset, dataSource, {
searchableSchema: true,
})?.schema
$: schemaFields = Object.values(schema || {}) $: schemaFields = Object.values(schema || {})
const saveFilter = async () => { const saveFilter = async () => {

View File

@ -17,7 +17,9 @@
component => component._component === "@budibase/standard-components/form" component => component._component === "@budibase/standard-components/form"
) )
$: datasource = getDatasourceForProvider($currentAsset, form) $: datasource = getDatasourceForProvider($currentAsset, form)
$: schema = getSchemaForDatasource($currentAsset, datasource, true).schema $: schema = getSchemaForDatasource($currentAsset, datasource, {
formSchema: true,
}).schema
$: options = getOptions(schema, type) $: options = getOptions(schema, type)
const getOptions = (schema, type) => { const getOptions = (schema, type) => {

View File

@ -15,7 +15,9 @@
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
$: datasource = getDatasourceForProvider($currentAsset, componentInstance) $: datasource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchemaForDatasource($currentAsset, datasource).schema $: schema = getSchemaForDatasource($currentAsset, datasource, {
searchableSchema: true,
}).schema
$: options = getOptions(datasource, schema || {}) $: options = getOptions(datasource, schema || {})
$: boundValue = getSelectedOption(value, options) $: boundValue = getSelectedOption(value, options)

View File

@ -116,6 +116,7 @@
$: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {}) $: schemaRules = parseRulesFromSchema(field, dataSourceSchema || {})
$: fieldType = type?.split("/")[1] || "string" $: fieldType = type?.split("/")[1] || "string"
$: constraintOptions = getConstraintsForType(fieldType) $: constraintOptions = getConstraintsForType(fieldType)
const getConstraintsForType = type => { const getConstraintsForType = type => {
return ConstraintMap[type] return ConstraintMap[type]
} }

View File

@ -39,7 +39,7 @@
$: datasource = $datasources.list.find(ds => ds._id === query.datasourceId) $: datasource = $datasources.list.find(ds => ds._id === query.datasourceId)
$: query.schema = fieldsToSchema(fields) $: query.schema = fieldsToSchema(fields)
$: datasourceType = datasource?.source $: datasourceType = datasource?.source
$: integrationInfo = $integrations[datasourceType] $: integrationInfo = datasourceType ? $integrations[datasourceType] : null
$: queryConfig = integrationInfo?.query $: queryConfig = integrationInfo?.query
$: shouldShowQueryConfig = queryConfig && query.queryVerb $: shouldShowQueryConfig = queryConfig && query.queryVerb
$: readQuery = query.queryVerb === "read" || query.readable $: readQuery = query.queryVerb === "read" || query.readable
@ -160,7 +160,7 @@
</div> </div>
<div class="viewer-controls"> <div class="viewer-controls">
<Heading size="S">Results</Heading> <Heading size="S">Results</Heading>
<ButtonGroup> <ButtonGroup gap="M">
<Button cta disabled={queryInvalid} on:click={saveQuery}> <Button cta disabled={queryInvalid} on:click={saveQuery}>
Save Query Save Query
</Button> </Button>

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/cli", "name": "@budibase/cli",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Budibase CLI, for developers, self hosting and migrations.", "description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js", "main": "src/index.js",
"bin": { "bin": {

View File

@ -15,7 +15,7 @@ const makeEnv = require("./makeEnv")
const axios = require("axios") const axios = require("axios")
const AnalyticsClient = require("../analytics/Client") const AnalyticsClient = require("../analytics/Client")
const BUDIBASE_SERVICES = ["app-service", "worker-service"] const BUDIBASE_SERVICES = ["app-service", "worker-service", "proxy-service"]
const ERROR_FILE = "docker-error.log" const ERROR_FILE = "docker-error.log"
const FILE_URLS = [ const FILE_URLS = [
"https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml", "https://raw.githubusercontent.com/Budibase/budibase/master/hosting/docker-compose.yaml",

View File

@ -851,16 +851,12 @@
"type": "color", "type": "color",
"label": "Color", "label": "Color",
"key": "color", "key": "color",
"showInBar": true, "showInBar": true
"barSeparator": false
}, },
{ {
"type": "boolean", "type": "boolean",
"label": "Show delete icon", "label": "Show delete icon",
"key": "closable", "key": "closable"
"showInBar": true,
"barIcon": "TagItalic",
"barTitle": "Show delete icon"
}, },
{ {
"type": "event", "type": "event",

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/client", "name": "@budibase/client",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"license": "MPL-2.0", "license": "MPL-2.0",
"module": "dist/budibase-client.js", "module": "dist/budibase-client.js",
"main": "dist/budibase-client.js", "main": "dist/budibase-client.js",
@ -19,9 +19,9 @@
"dev:builder": "rollup -cw" "dev:builder": "rollup -cw"
}, },
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.58-alpha.7", "@budibase/bbui": "^1.0.65",
"@budibase/frontend-core": "^1.0.58-alpha.7", "@budibase/frontend-core": "^1.0.65",
"@budibase/string-templates": "^1.0.58-alpha.7", "@budibase/string-templates": "^1.0.65",
"@spectrum-css/button": "^3.0.3", "@spectrum-css/button": "^3.0.3",
"@spectrum-css/card": "^3.0.3", "@spectrum-css/card": "^3.0.3",
"@spectrum-css/divider": "^1.0.3", "@spectrum-css/divider": "^1.0.3",

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
{ {
"name": "@budibase/frontend-core", "name": "@budibase/frontend-core",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Budibase frontend core libraries used in builder and client", "description": "Budibase frontend core libraries used in builder and client",
"author": "Budibase", "author": "Budibase",
"license": "MPL-2.0", "license": "MPL-2.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"dependencies": { "dependencies": {
"@budibase/bbui": "^1.0.58-alpha.7", "@budibase/bbui": "^1.0.65",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"svelte": "^3.46.2" "svelte": "^3.46.2"
} }

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/server", "name": "@budibase/server",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Budibase Web Server", "description": "Budibase Web Server",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -70,9 +70,9 @@
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@apidevtools/swagger-parser": "^10.0.3", "@apidevtools/swagger-parser": "^10.0.3",
"@budibase/backend-core": "^1.0.58-alpha.7", "@budibase/backend-core": "^1.0.65",
"@budibase/client": "^1.0.58-alpha.7", "@budibase/client": "^1.0.65",
"@budibase/string-templates": "^1.0.58-alpha.7", "@budibase/string-templates": "^1.0.65",
"@bull-board/api": "^3.7.0", "@bull-board/api": "^3.7.0",
"@bull-board/koa": "^3.7.0", "@bull-board/koa": "^3.7.0",
"@elastic/elasticsearch": "7.10.0", "@elastic/elasticsearch": "7.10.0",

View File

@ -1,6 +1,8 @@
const joiValidator = require("../../../middleware/joi-validator") const joiValidator = require("../../../middleware/joi-validator")
const Joi = require("joi") const Joi = require("joi")
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
exports.queryValidation = () => { exports.queryValidation = () => {
return Joi.object({ return Joi.object({
_id: Joi.string(), _id: Joi.string(),
@ -18,7 +20,7 @@ exports.queryValidation = () => {
queryVerb: Joi.string().allow().required(), queryVerb: Joi.string().allow().required(),
extra: Joi.object().optional(), extra: Joi.object().optional(),
schema: Joi.object({}).required().unknown(true), schema: Joi.object({}).required().unknown(true),
transformer: Joi.string().optional(), transformer: OPTIONAL_STRING,
flags: Joi.object().optional(), flags: Joi.object().optional(),
}) })
} }
@ -31,18 +33,18 @@ exports.generateQueryValidation = () => {
exports.generateQueryPreviewValidation = () => { exports.generateQueryPreviewValidation = () => {
// prettier-ignore // prettier-ignore
return joiValidator.body(Joi.object({ return joiValidator.body(Joi.object({
_id: Joi.string().optional(), _id: OPTIONAL_STRING,
_rev: Joi.string().optional(), _rev: OPTIONAL_STRING,
readable: Joi.boolean().optional(), readable: Joi.boolean().optional(),
fields: Joi.object().required(), fields: Joi.object().required(),
queryVerb: Joi.string().allow().required(), queryVerb: Joi.string().required(),
name: Joi.string().required(), name: OPTIONAL_STRING,
flags: Joi.object().optional(), flags: Joi.object().optional(),
schema: Joi.object().optional(), schema: Joi.object().optional(),
extra: Joi.object().optional(), extra: Joi.object().optional(),
datasourceId: Joi.string().required(), datasourceId: Joi.string().required(),
transformer: Joi.string().optional(), transformer: OPTIONAL_STRING,
parameters: Joi.object({}).required().unknown(true), parameters: Joi.object({}).required().unknown(true),
queryId: Joi.string().optional(), queryId: OPTIONAL_STRING,
})) }))
} }

View File

@ -235,7 +235,7 @@ class QueryBuilder {
if (this.sort) { if (this.sort) {
const order = this.sortOrder === "descending" ? "-" : "" const order = this.sortOrder === "descending" ? "-" : ""
const type = `<${this.sortType}>` const type = `<${this.sortType}>`
body.sort = `${order}${this.sort.replace(/ /, "_")}${type}` body.sort = `${order}${this.sort.replace(/ /g, "_")}${type}`
} }
return body return body
} }

View File

@ -96,7 +96,7 @@ exports.createAllSearchIndex = async () => {
function idx(input, prev) { function idx(input, prev) {
for (let key of Object.keys(input)) { for (let key of Object.keys(input)) {
let idxKey = prev != null ? `${prev}.${key}` : key let idxKey = prev != null ? `${prev}.${key}` : key
idxKey = idxKey.replace(/ /, "_") idxKey = idxKey.replace(/ /g, "_")
if (Array.isArray(input[key])) { if (Array.isArray(input[key])) {
for (let val of input[key]) { for (let val of input[key]) {
if (typeof val !== "object") { if (typeof val !== "object") {

View File

@ -1,6 +1,6 @@
{ {
"name": "@budibase/string-templates", "name": "@budibase/string-templates",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Handlebars wrapper for Budibase templating.", "description": "Handlebars wrapper for Budibase templating.",
"main": "src/index.cjs", "main": "src/index.cjs",
"module": "dist/bundle.mjs", "module": "dist/bundle.mjs",

View File

@ -3,7 +3,7 @@ const { registerAll, registerMinimum } = require("./helpers/index")
const processors = require("./processors") const processors = require("./processors")
const { atob, btoa } = require("./utilities") const { atob, btoa } = require("./utilities")
const manifest = require("../manifest.json") const manifest = require("../manifest.json")
const { FIND_HBS_REGEX, FIND_DOUBLE_HBS_REGEX } = require("./utilities") const { FIND_HBS_REGEX, findDoubleHbsInstances } = require("./utilities")
const hbsInstance = handlebars.create() const hbsInstance = handlebars.create()
registerAll(hbsInstance) registerAll(hbsInstance)
@ -163,8 +163,7 @@ module.exports.processStringSync = (string, context, opts) => {
* @param string the string to have double HBS statements converted to triple. * @param string the string to have double HBS statements converted to triple.
*/ */
module.exports.disableEscaping = string => { module.exports.disableEscaping = string => {
let regexp = new RegExp(FIND_DOUBLE_HBS_REGEX) const matches = findDoubleHbsInstances(string)
const matches = string.match(regexp)
if (matches == null) { if (matches == null) {
return string return string
} }

View File

@ -1,7 +1,25 @@
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
module.exports.FIND_DOUBLE_HBS_REGEX = /(?<!{){{[^{}]+}}(?!})/g module.exports.FIND_TRIPLE_HBS_REGEX = /{{{([^{].*?)}}}/g
// originally this could be done with a single regex using look behinds
// but safari does not support this feature
// original regex: /(?<!{){{[^{}]+}}(?!})/g
module.exports.findDoubleHbsInstances = string => {
let copied = string
const doubleRegex = new RegExp(exports.FIND_HBS_REGEX)
const regex = new RegExp(exports.FIND_TRIPLE_HBS_REGEX)
const tripleMatches = copied.match(regex)
// remove triple braces
if (tripleMatches) {
tripleMatches.forEach(match => {
copied = copied.replace(match, "")
})
}
const doubleMatches = copied.match(doubleRegex)
return doubleMatches ? doubleMatches : []
}
module.exports.isAlphaNumeric = char => { module.exports.isAlphaNumeric = char => {
return char.match(ALPHA_NUMERIC_REGEX) return char.match(ALPHA_NUMERIC_REGEX)

View File

@ -1,7 +1,7 @@
{ {
"name": "@budibase/worker", "name": "@budibase/worker",
"email": "hi@budibase.com", "email": "hi@budibase.com",
"version": "1.0.58-alpha.7", "version": "1.0.65",
"description": "Budibase background service", "description": "Budibase background service",
"main": "src/index.ts", "main": "src/index.ts",
"repository": { "repository": {
@ -34,8 +34,8 @@
"author": "Budibase", "author": "Budibase",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@budibase/backend-core": "^1.0.58-alpha.7", "@budibase/backend-core": "^1.0.65",
"@budibase/string-templates": "^1.0.58-alpha.7", "@budibase/string-templates": "^1.0.65",
"@koa/router": "^8.0.0", "@koa/router": "^8.0.0",
"@sentry/node": "^6.0.0", "@sentry/node": "^6.0.0",
"@techpass/passport-openidconnect": "^0.3.0", "@techpass/passport-openidconnect": "^0.3.0",

View File

@ -27,8 +27,10 @@ describe("/api/global/email", () => {
userId: user._id, userId: user._id,
}) })
.set(config.defaultHeaders()) .set(config.defaultHeaders())
.expect("Content-Type", /json/) // ethereal hiccup, can't test right now
.expect(200) if (res.status >= 300) {
return
}
expect(res.body.message).toBeDefined() expect(res.body.message).toBeDefined()
const testUrl = nodemailer.getTestMessageUrl(res.body) const testUrl = nodemailer.getTestMessageUrl(res.body)
console.log(`${purpose} URL: ${testUrl}`) console.log(`${purpose} URL: ${testUrl}`)
@ -37,7 +39,7 @@ describe("/api/global/email", () => {
text = await response.text() text = await response.text()
} catch (err) { } catch (err) {
// ethereal hiccup, can't test right now // ethereal hiccup, can't test right now
if (parseInt(err.status) >= 400) { if (parseInt(err.status) >= 300) {
return return
} else { } else {
throw err throw err