Merge branch 'develop' of github.com:Budibase/budibase into feature/test-image

This commit is contained in:
mike12345567 2022-12-09 17:23:26 +00:00
commit e8667fd04d
113 changed files with 5278 additions and 3545 deletions

View File

@ -4,6 +4,9 @@ metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null
labels:
io.kompose.service: app-service
@ -156,8 +159,24 @@ spec:
- name: ELASTIC_APM_SERVER_URL
value: {{ .Values.globals.elasticApmServerUrl | quote }}
{{ end }}
{{ if .Values.globals.globalAgentHttpProxy }}
- name: GLOBAL_AGENT_HTTP_PROXY
value: {{ .Values.globals.globalAgentHttpProxy | quote }}
{{ end }}
{{ if .Values.globals.globalAgentHttpsProxy }}
- name: GLOBAL_AGENT_HTTPS_PROXY
value: {{ .Values.globals.globalAgentHttpsProxy | quote }}
{{ end }}
{{ if .Values.globals.globalAgentNoProxy }}
- name: GLOBAL_AGENT_NO_PROXY
value: {{ .Values.globals.globalAgentNoProxy | quote }}
{{ end }}
- name: CDN_URL
value: {{ .Values.globals.cdnUrl }}
{{ if .Values.services.tlsRejectUnauthorized }}
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: {{ .Values.services.tlsRejectUnauthorized }}
{{ end }}
image: budibase/apps:{{ .Values.globals.appVersion }}
imagePullPolicy: Always

View File

@ -42,6 +42,7 @@ spec:
secretKeyRef:
name: {{ template "budibase.fullname" . }}
key: objectStoreSecret
image: minio/minio
imagePullPolicy: ""
livenessProbe:

View File

@ -4,6 +4,9 @@ metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null
labels:
app.kubernetes.io/name: budibase-proxy

View File

@ -60,5 +60,6 @@ spec:
- name: redis-data
persistentVolumeClaim:
claimName: redis-data
status: {}
{{- end }}

View File

@ -4,6 +4,9 @@ metadata:
annotations:
kompose.cmd: kompose convert
kompose.version: 1.21.0 (992df58d8)
{{ if .Values.globals.logAnnotations }}
{{ toYaml .Values.globals.logAnnotations | indent 4 }}
{{ end }}
creationTimestamp: null
labels:
io.kompose.service: worker-service
@ -147,8 +150,24 @@ spec:
- name: ELASTIC_APM_SERVER_URL
value: {{ .Values.globals.elasticApmServerUrl | quote }}
{{ end }}
{{ if .Values.globals.globalAgentHttpProxy }}
- name: GLOBAL_AGENT_HTTP_PROXY
value: {{ .Values.globals.globalAgentHttpProxy | quote }}
{{ end }}
{{ if .Values.globals.globalAgentHttpsProxy }}
- name: GLOBAL_AGENT_HTTPS_PROXY
value: {{ .Values.globals.globalAgentHttpsProxy | quote }}
{{ end }}
{{ if .Values.globals.globalAgentNoProxy }}
- name: GLOBAL_AGENT_NO_PROXY
value: {{ .Values.globals.globalAgentNoProxy | quote }}
{{ end }}
- name: CDN_URL
value: {{ .Values.globals.cdnUrl }}
{{ if .Values.services.tlsRejectUnauthorized }}
- name: NODE_TLS_REJECT_UNAUTHORIZED
value: {{ .Values.services.tlsRejectUnauthorized }}
{{ end }}
image: budibase/worker:{{ .Values.globals.appVersion }}
imagePullPolicy: Always

View File

@ -22,6 +22,12 @@ serviceAccount:
podAnnotations: {}
# logAnnotations:
# co.elastic.logs/multiline.type: pattern
# co.elastic.logs/multiline.pattern: '^[[:space:]]'
# co.elastic.logs/multiline.negate: false
# co.elastic.logs/multiline.match: after
podSecurityContext:
{}
# fsGroup: 2000
@ -106,10 +112,14 @@ globals:
# elasticApmEnabled:
# elasticApmSecretToken:
# elasticApmServerUrl:
# globalAgentHttpProxy:
# globalAgentHttpsProxy:
# globalAgentNoProxy:
services:
budibaseVersion: latest
dns: cluster.local
# tlsRejectUnauthorized: 0
proxy:
port: 10000

View File

@ -47,7 +47,8 @@ ADD hosting/single/nginx/nginx.conf /etc/nginx
ADD hosting/single/nginx/nginx-default-site.conf /etc/nginx/sites-enabled/default
RUN mkdir -p /var/log/nginx && \
touch /var/log/nginx/error.log && \
touch /var/run/nginx.pid
touch /var/run/nginx.pid && \
usermod -a -G tty www-data
WORKDIR /
RUN mkdir -p scripts/integrations/oracle

View File

@ -2,7 +2,8 @@ server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
error_log /dev/stderr warn;
access_log /dev/stdout main;
client_max_body_size 1000m;
ignore_invalid_headers off;
proxy_buffering off;

View File

@ -1,5 +1,5 @@
user www-data www-data;
error_log /var/log/nginx/error.log;
error_log /dev/stderr warn;
pid /var/run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 8192;

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/backend-core",
"version": "2.1.40-alpha.7",
"version": "2.1.46-alpha.1",
"description": "Budibase backend core libraries used in server and worker",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
@ -16,11 +16,11 @@
"prepack": "cp package.json dist",
"build": "tsc -p tsconfig.build.json",
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
"test": "jest --coverage",
"test": "jest --coverage --maxWorkers=2",
"test:watch": "jest --watchAll"
},
"dependencies": {
"@budibase/types": "2.1.40-alpha.7",
"@budibase/types": "2.1.46-alpha.1",
"@shopify/jest-koa-mocks": "5.0.1",
"@techpass/passport-openidconnect": "0.3.2",
"aws-sdk": "2.1030.0",

View File

@ -1,9 +1,9 @@
import { ssoCallbackUrl } from "./utils"
import { authenticateThirdParty } from "./third-party-common"
import { authenticateThirdParty, SaveUserFunction } from "./third-party-common"
import { ConfigType, GoogleConfig, Database, SSOProfile } from "@budibase/types"
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
export function buildVerifyFn(saveUserFn?: Function) {
export function buildVerifyFn(saveUserFn?: SaveUserFunction) {
return (
accessToken: string,
refreshToken: string,
@ -39,7 +39,7 @@ export function buildVerifyFn(saveUserFn?: Function) {
export async function strategyFactory(
config: GoogleConfig["config"],
callbackUrl: string,
saveUserFn?: Function
saveUserFn?: SaveUserFunction
) {
try {
const { clientID, clientSecret } = config

View File

@ -1,5 +1,5 @@
import fetch from "node-fetch"
import { authenticateThirdParty } from "./third-party-common"
import { authenticateThirdParty, SaveUserFunction } from "./third-party-common"
import { ssoCallbackUrl } from "./utils"
import {
Config,
@ -17,7 +17,7 @@ type JwtClaims = {
email: string
}
export function buildVerifyFn(saveUserFn?: Function) {
export function buildVerifyFn(saveUserFn?: SaveUserFunction) {
/**
* @param {*} issuer The identity provider base URL
* @param {*} sub The user ID
@ -106,7 +106,7 @@ function validEmail(value: string) {
*/
export async function strategyFactory(
config: OIDCConfiguration,
saveUserFn?: Function
saveUserFn?: SaveUserFunction
) {
try {
const verify = buildVerifyFn(saveUserFn)

View File

@ -9,6 +9,17 @@ import fetch from "node-fetch"
import { ThirdPartyUser } from "@budibase/types"
const jwt = require("jsonwebtoken")
type SaveUserOpts = {
requirePassword?: boolean
hashPassword?: boolean
currentUserId?: string
}
export type SaveUserFunction = (
user: ThirdPartyUser,
opts: SaveUserOpts
) => Promise<any>
/**
* Common authentication logic for third parties. e.g. OAuth, OIDC.
*/
@ -16,7 +27,7 @@ export async function authenticateThirdParty(
thirdPartyUser: ThirdPartyUser,
requireLocalAccount: boolean = true,
done: Function,
saveUserFn?: Function
saveUserFn?: SaveUserFunction
) {
if (!saveUserFn) {
throw new Error("Save user function must be provided")
@ -81,7 +92,7 @@ export async function authenticateThirdParty(
// create or sync the user
try {
await saveUserFn(dbUser, false, false)
await saveUserFn(dbUser, { hashPassword: false, requirePassword: false })
} catch (err: any) {
return authError(done, err)
}

View File

@ -1521,13 +1521,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==
async@~2.1.4:
version "2.1.5"
resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc"
integrity sha512-+g/Ncjbx0JSq2Mk03WQkyKvNh5q9Qvyo/RIqIqnmC5feJY70PNl2ESwZU2BhAB+AZPkHNzzyC2Dq2AS5VnTKhQ==
dependencies:
lodash "^4.14.0"
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@ -2669,32 +2662,6 @@ globals@^11.1.0:
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
google-auth-library@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-0.10.0.tgz#6e15babee85fd1dd14d8d128a295b6838d52136e"
integrity sha512-KM54Y9GhdAzfXUHmWEoYmaOykSLuMG7W4HvVLYqyogxOyE6px8oSS8W13ngqW0oDGZ915GFW3V6OM6+qcdvPOA==
dependencies:
gtoken "^1.2.1"
jws "^3.1.4"
lodash.noop "^3.0.1"
request "^2.74.0"
google-p12-pem@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-0.1.2.tgz#33c46ab021aa734fa0332b3960a9a3ffcb2f3177"
integrity sha512-puhMlJ2+E/rgvxWaqgN/nC7x623OAE8MR9vBUqxF0inCE7HoVfCHvTeQ9+BR+rj9KM0fIg6XV6tmbt7XHHssoQ==
dependencies:
node-forge "^0.7.1"
googleapis@^16.0.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-16.1.0.tgz#0f19f2d70572d918881a0f626e3b1a2fa8629576"
integrity sha512-5czmF7xkIlJKc1+/+5tltrI1skoR3HKtkDOld9rk+DOucTpZRjOhCoJzoSjxB3M8rP2tEb1VIr1TPyzR3V2PUQ==
dependencies:
async "~2.1.4"
google-auth-library "~0.10.0"
string-template "~1.0.0"
got@^9.6.0:
version "9.6.0"
resolved "https://registry.yarnpkg.com/got/-/got-9.6.0.tgz#edf45e7d67f99545705de1f7bbeeeb121765ed85"
@ -2717,16 +2684,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.9:
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
gtoken@^1.2.1:
version "1.2.3"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-1.2.3.tgz#5509571b8afd4322e124cf66cf68115284c476d8"
integrity sha512-wQAJflfoqSgMWrSBk9Fg86q+sd6s7y6uJhIvvIPz++RElGlMtEqsdAR2oWwZ/WTEtp7P9xFbJRrT976oRgzJ/w==
dependencies:
google-p12-pem "^0.1.0"
jws "^3.0.0"
mime "^1.4.1"
request "^2.72.0"
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
@ -3609,7 +3566,7 @@ jwa@^1.4.1:
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^3.0.0, jws@^3.1.4, jws@^3.2.2:
jws@^3.2.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304"
integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==
@ -3855,11 +3812,6 @@ lodash.memoize@4.x:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
lodash.noop@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash.noop/-/lodash.noop-3.0.1.tgz#38188f4d650a3a474258439b96ec45b32617133c"
integrity sha512-TmYdmu/pebrdTIBDK/FDx9Bmfzs9x0sZG6QIJuMDTqEPfeciLcN13ij+cOd0i9vwJfBtbG9UQ+C7MkXgYxrIJg==
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
@ -3870,7 +3822,7 @@ lodash.pick@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
lodash@4.17.21, lodash@^4.14.0, lodash@^4.17.21:
lodash@4.17.21, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -3982,7 +3934,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24,
dependencies:
mime-db "1.52.0"
mime@^1.3.4, mime@^1.4.1:
mime@^1.3.4:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@ -4129,11 +4081,6 @@ node-fetch@2.6.7, node-fetch@^2.6.7:
dependencies:
whatwg-url "^5.0.0"
node-forge@^0.7.1:
version "0.7.6"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
node-gyp-build-optional-packages@5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17"
@ -4356,14 +4303,6 @@ parseurl@^1.3.2, parseurl@^1.3.3:
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
passport-google-auth@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/passport-google-auth/-/passport-google-auth-1.0.2.tgz#8b300b5aa442ef433de1d832ed3112877d0b2938"
integrity sha512-cfAqna6jZLyMEwUdd4PIwAh2mQKQVEDAaRIaom1pG6h4x4Gwjllf/Jflt3TkR1Sen5Rkvr3l7kSXCWE1EKkh8g==
dependencies:
googleapis "^16.0.0"
passport-strategy "1.x"
passport-google-oauth1@1.x.x:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-google-oauth1/-/passport-google-oauth1-1.0.0.tgz#af74a803df51ec646f66a44d82282be6f108e0cc"
@ -4426,7 +4365,7 @@ passport-oauth2@1.x.x:
uid2 "0.0.x"
utils-merge "1.x.x"
passport-strategy@1.x, passport-strategy@1.x.x, passport-strategy@^1.0.0:
passport-strategy@1.x.x, passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==
@ -4938,7 +4877,7 @@ remove-trailing-slash@^0.1.1:
resolved "https://registry.yarnpkg.com/remove-trailing-slash/-/remove-trailing-slash-0.1.1.tgz#be2285a59f39c74d1bce4f825950061915e3780d"
integrity sha512-o4S4Qh6L2jpnCy83ysZDau+VORNvnFw07CKSAymkd6ICNVEPisMyzlc00KlvvicsxKck94SEwhDnMNdICzO+tA==
request@^2.72.0, request@^2.74.0, request@^2.88.0:
request@^2.88.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -5204,11 +5143,6 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
string-template@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/string-template/-/string-template-1.0.0.tgz#9e9f2233dc00f218718ec379a28a5673ecca8b96"
integrity sha512-SLqR3GBUXuoPP5MmYtD7ompvXiG87QjT6lzOszyXjTM86Uu7At7vNnt2xgyTLq5o9T4IxTYFyGxcULqpsmsfdg==
"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.",
"version": "2.1.40-alpha.7",
"version": "2.1.46-alpha.1",
"license": "MPL-2.0",
"svelte": "src/index.js",
"module": "dist/bbui.es.js",
@ -38,7 +38,7 @@
],
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@budibase/string-templates": "2.1.40-alpha.7",
"@budibase/string-templates": "2.1.46-alpha.1",
"@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/avatar": "^3.0.2",
@ -67,19 +67,19 @@
"@spectrum-css/search": "^3.0.2",
"@spectrum-css/sidenav": "^3.0.2",
"@spectrum-css/slider": "3.0.1",
"@spectrum-css/statuslight": "^3.0.2",
"@spectrum-css/stepper": "^3.0.3",
"@spectrum-css/switch": "^1.0.2",
"@spectrum-css/table": "^3.0.1",
"@spectrum-css/tabs": "^3.2.12",
"@spectrum-css/tags": "^3.0.2",
"@spectrum-css/textfield": "^3.0.1",
"@spectrum-css/toast": "^3.0.1",
"@spectrum-css/tooltip": "^3.0.3",
"@spectrum-css/treeview": "^3.0.2",
"@spectrum-css/typography": "^3.0.1",
"@spectrum-css/underlay": "^2.0.9",
"@spectrum-css/vars": "^3.0.1",
"@spectrum-css/statuslight": "3.0.2",
"@spectrum-css/stepper": "3.0.3",
"@spectrum-css/switch": "1.0.2",
"@spectrum-css/table": "3.0.1",
"@spectrum-css/tabs": "3.2.12",
"@spectrum-css/tags": "3.0.2",
"@spectrum-css/textfield": "3.0.1",
"@spectrum-css/toast": "3.0.1",
"@spectrum-css/tooltip": "3.0.3",
"@spectrum-css/treeview": "3.0.2",
"@spectrum-css/typography": "3.0.1",
"@spectrum-css/underlay": "2.0.9",
"@spectrum-css/vars": "3.0.1",
"dayjs": "^1.10.4",
"easymde": "^2.16.1",
"svelte-flatpickr": "^3.2.3",

View File

@ -205,7 +205,10 @@
width: 100%;
}
.spectrum-Popover.auto-width :global(.spectrum-Menu-itemLabel) {
max-width: 400px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.spectrum-Picker {
width: 100%;

View File

@ -28,9 +28,9 @@
let loading = false
$: confirmDisabled = disabled || loading
async function secondary() {
async function secondary(e) {
loading = true
if (!secondaryAction || (await secondaryAction()) !== false) {
if (!secondaryAction || (await secondaryAction(e)) !== false) {
hide()
}
loading = false

View File

@ -9,7 +9,7 @@
height: 100%;
width: 100%;
opacity: 0;
background-color: var(--spectrum-global-color-gray-300) !important;
background-color: var(--spectrum-global-color-gray-200) !important;
border-radius: 7px;
overflow: hidden;
position: relative;
@ -31,8 +31,8 @@
background-image: linear-gradient(
90deg,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 0.2) 20%,
rgba(255, 255, 255, 0.5) 60%,
rgba(255, 255, 255, 0.15) 20%,
rgba(255, 255, 255, 0.3) 60%,
rgba(255, 255, 255, 0)
);
animation: shimmer 2s infinite;
@ -44,7 +44,7 @@
opacity: 0;
}
100% {
opacity: 1;
opacity: 0.75;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -2,20 +2,20 @@ import filterTests from "../../support/filterTests"
const interact = require('../../support/interact')
filterTests(["smoke", "all"], () => {
context("User Management", () => {
xcontext("User Management", () => {
before(() => {
cy.login()
cy.deleteApp("Cypress Tests")
cy.createApp("Cypress Tests", false)
})
it("should create a user via basic onboarding", () => {
xit("should create a user via basic onboarding", () => {
cy.visit(`${Cypress.config().baseUrl}/builder`, { timeout: 5000})
cy.createUser("bbuser@test.com")
cy.get(interact.SPECTRUM_TABLE).should("contain", "bbuser")
})
it("should confirm App User role for a New User", () => {
xit("should confirm App User role for a New User", () => {
cy.contains("bbuser").click()
cy.get(".spectrum-Form-itemField").eq(3).should('contain', 'App User')
@ -166,7 +166,7 @@ filterTests(["smoke", "all"], () => {
})
})
it("Should edit user details within user details page", () => {
xit("Should edit user details within user details page", () => {
// Add First name
cy.get(interact.FIELD, { timeout: 1000 }).eq(1).within(() => {
cy.wait(500)
@ -190,7 +190,7 @@ filterTests(["smoke", "all"], () => {
})
})
it("should reset the users password", () => {
xit("should reset the users password", () => {
cy.get(".title").within(() => {
cy.get(interact.SPECTRUM_ICON).click({ force: true })
})
@ -230,7 +230,7 @@ filterTests(["smoke", "all"], () => {
cy.login()
})
it("should delete a user", () => {
xit("should delete a user", () => {
cy.deleteUser("bbuser@test.com")
cy.get(interact.SPECTRUM_TABLE, { timeout: 4000 }).should("not.have.text", "bbuser")
})

View File

@ -140,7 +140,8 @@ filterTests(["all"], () => {
})
cy.visit(`${Cypress.config().baseUrl}/builder`)
cy.get(".appTable .app-row-actions button")
cy.wait(1000)
cy.get(".appTable .app-row-actions button", { timeout: 10000 })
.contains("Manage")
.eq(0)
.click({ force: true })

View File

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

View File

@ -481,6 +481,7 @@ const getSelectedRowsBindings = asset => {
block._id + "-table"
)}.${makePropSafe("selectedRows")}`,
readableBinding: `${block._instanceName}.Selected rows`,
category: "Selected rows",
}))
)
}
@ -1004,7 +1005,10 @@ const bindingReplacement = (
* {{ literal [componentId] }}
*/
const extractLiteralHandlebarsID = value => {
return value?.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
if (!value || typeof value !== "string") {
return null
}
return value.match(/{{\s*literal\s*\[+([^\]]+)].*}}/)?.[1]
}
/**

View File

@ -25,6 +25,7 @@ import {
DB_TYPE_INTERNAL,
DB_TYPE_EXTERNAL,
} from "constants/backend"
import { getSchemaForDatasource } from "builderStore/dataBinding"
const INITIAL_FRONTEND_STATE = {
apps: [],
@ -524,7 +525,9 @@ export const getFrontendStore = () => {
// Generate default props
let props = { ...presetProps }
settings.forEach(setting => {
if (setting.defaultValue !== undefined) {
if (setting.type === "multifield" && setting.selectAllFields) {
props[setting.key] = Object.keys(defaultDatasource.schema || {})
} else if (setting.defaultValue !== undefined) {
props[setting.key] = setting.defaultValue
}
})
@ -1041,6 +1044,27 @@ export const getFrontendStore = () => {
if (component[name] === value) {
return false
}
const settings = getComponentSettings(component._component)
const updatedSetting = settings.find(setting => setting.key === name)
if (
updatedSetting?.type === "dataSource" ||
updatedSetting?.type === "table"
) {
const { schema } = getSchemaForDatasource(null, value)
const columnNames = Object.keys(schema || {})
const multifieldKeysToSelectAll = settings
.filter(setting => {
return setting.type === "multifield" && setting.selectAllFields
})
.map(setting => setting.key)
multifieldKeysToSelectAll.forEach(key => {
component[key] = columnNames
})
}
component[name] = value
})
},

View File

@ -38,13 +38,15 @@
export let testData
export let schemaProperties
export let isTestModal = false
let webhookModal
let drawer
let tempFilters = lookForFilters(schemaProperties) || []
let fillWidth = true
let codeBindingOpen = false
let inputData
$: filters = lookForFilters(schemaProperties) || []
$: tempFilters = filters
$: stepId = block.stepId
$: bindings = getAvailableBindings(
block || $automationStore.selectedBlock,
@ -222,16 +224,17 @@
{:else if value.customType === "filters"}
<ActionButton on:click={drawer.show}>Define filters</ActionButton>
<Drawer bind:this={drawer} {fillWidth} title="Filtering">
<Button cta slot="buttons" on:click={() => saveFilters(key)}
>Save</Button
>
<Button cta slot="buttons" on:click={() => saveFilters(key)}>
Save
</Button>
<FilterDrawer
slot="body"
bind:filters={tempFilters}
{filters}
{bindings}
{schemaFields}
panel={AutomationBindingPanel}
fillWidth
on:change={e => (tempFilters = e.detail)}
/>
</Drawer>
{:else if value.customType === "password"}

View File

@ -25,6 +25,7 @@
import { API } from "api"
let hideAutocolumns = true
let filters
$: isUsersTable = $tables.selected?._id === TableNames.USERS
$: type = $tables.selected?.type
@ -36,6 +37,7 @@
$: hasCols = checkHasCols(schema)
$: hasRows = !!$fetch.rows?.length
$: showError($fetch.error)
$: id, (filters = null)
const showError = error => {
if (error) {
@ -102,8 +104,9 @@
// Fetch data whenever filters change
const onFilter = e => {
filters = e.detail
fetch.update({
filter: e.detail,
filter: filters,
})
}
@ -117,6 +120,12 @@
const onUpdateRows = () => {
fetch.refresh()
}
// When importing new rows it is better to reinitialise request/paging data.
// Not doing so causes inconsistency in paging behaviour and content.
const onImportData = () => {
fetch.getInitialData()
}
</script>
<div>
@ -169,7 +178,7 @@
<ImportButton
disabled={$tables.selected?._id === "ta_users"}
tableId={$tables.selected?._id}
on:updaterows={onUpdateRows}
on:importrows={onImportData}
/>
<ExportButton
disabled={!hasRows || !hasCols}
@ -178,6 +187,7 @@
{#key id}
<TableFilterButton
{schema}
{filters}
on:change={onFilter}
disabled={!hasCols}
/>

View File

@ -4,6 +4,7 @@
import { Table, Modal, Heading, notifications, Layout } from "@budibase/bbui"
import { API } from "api"
import Spinner from "components/common/Spinner.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import DeleteRowsButton from "./buttons/DeleteRowsButton.svelte"
import CreateEditRow from "./modals/CreateEditRow.svelte"
import CreateEditUser from "./modals/CreateEditUser.svelte"
@ -34,8 +35,8 @@
let editRowModal
let editColumnModal
let customRenderers = []
let confirmDelete
$: isInternal = type !== "external"
$: isUsersTable = tableId === TableNames.USERS
$: data && resetSelectedRows()
$: editRowComponent = isUsersTable ? CreateEditUser : CreateEditRow
@ -89,15 +90,17 @@
)
}
const deleteRows = async () => {
const deleteRows = async targetRows => {
try {
await API.deleteRows({
tableId,
rows: selectedRows,
rows: targetRows,
})
data = data.filter(row => !selectedRows.includes(row))
notifications.success(`Successfully deleted ${selectedRows.length} rows`)
selectedRows = []
const deletedRowIds = targetRows.map(row => row._id)
data = data.filter(row => deletedRowIds.indexOf(row._id))
notifications.success(`Successfully deleted ${targetRows.length} rows`)
} catch (error) {
notifications.error("Error deleting rows")
}
@ -133,7 +136,14 @@
<div class="popovers">
<slot />
{#if !isUsersTable && selectedRows.length > 0}
<DeleteRowsButton on:updaterows {selectedRows} {deleteRows} />
<DeleteRowsButton
on:updaterows
{selectedRows}
deleteRows={async rows => {
await deleteRows(rows)
resetSelectedRows()
}}
/>
{/if}
</div>
</Layout>
@ -164,8 +174,33 @@
</Layout>
<Modal bind:this={editRowModal}>
<svelte:component this={editRowComponent} on:updaterows row={editableRow} />
<svelte:component
this={editRowComponent}
on:updaterows
on:deleteRows={() => {
confirmDelete.show()
}}
row={editableRow}
/>
</Modal>
<ConfirmDialog
bind:this={confirmDelete}
okText="Delete"
onOk={async () => {
if (editableRow) {
await deleteRows([editableRow])
}
editableRow = undefined
}}
onCancel={async () => {
editRow(editableRow)
}}
title="Confirm Deletion"
>
Are you sure you want to delete this row?
</ConfirmDialog>
<Modal bind:this={editColumnModal}>
<CreateEditColumn
field={editableColumn}

View File

@ -11,7 +11,7 @@
let modal
async function confirmDeletion() {
await deleteRows()
await deleteRows(selectedRows)
modal?.hide()
dispatch("updaterows")
}

View File

@ -12,5 +12,5 @@
Import
</ActionButton>
<Modal bind:this={modal}>
<ImportModal {tableId} on:updaterows />
<ImportModal {tableId} on:importrows />
</Modal>

View File

@ -8,9 +8,10 @@
export let disabled = false
const dispatch = createEventDispatcher()
let modal
let tempValue = filters || []
let modal
$: tempValue = filters || []
$: schemaFields = Object.values(schema || {})
</script>
@ -34,8 +35,9 @@
<div class="wrapper">
<FilterDrawer
allowBindings={false}
bind:filters={tempValue}
{filters}
{schemaFields}
on:change={e => (tempValue = e.detail)}
/>
</div>
</ModalContent>

View File

@ -50,22 +50,34 @@
}
</script>
<ModalContent
title={creating ? "Create Row" : "Edit Row"}
confirmText={creating ? "Create Row" : "Save Row"}
onConfirm={saveRow}
>
{#each tableSchema as [key, meta]}
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
<div>
<RowFieldControl error={errors[key]} {meta} bind:value={row[key]} />
</div>
{/if}
{/each}
</ModalContent>
<span class="modal-wrap">
<ModalContent
title={creating ? "Create Row" : "Edit Row"}
confirmText={creating ? "Create Row" : "Save Row"}
onConfirm={saveRow}
showCancelButton={creating}
showSecondaryButton={!creating}
secondaryButtonWarning={!creating}
secondaryButtonText="Delete"
secondaryAction={() => {
dispatch("deleteRows", row)
}}
>
{#each tableSchema as [key, meta]}
{#if !meta.autocolumn && meta.type !== FORMULA_TYPE}
<div>
<RowFieldControl error={errors[key]} {meta} bind:value={row[key]} />
</div>
{/if}
{/each}
</ModalContent>
</span>
<style>
div {
min-width: 0;
}
.modal-wrap :global(.secondary-action) {
margin-right: unset;
}
</style>

View File

@ -29,7 +29,7 @@
}
// Always refresh rows just to be sure
dispatch("updaterows")
dispatch("importrows")
}
</script>

View File

@ -45,6 +45,23 @@
const touched = writable({})
function invalidThroughTable({ through, throughTo, throughFrom }) {
// need to know the foreign key columns to check error
if (!through || !throughTo || !throughFrom) {
return false
}
const throughTable = plusTables.find(tbl => tbl._id === through)
const otherColumns = Object.values(throughTable.schema).filter(
col => col.name !== throughFrom && col.name !== throughTo
)
for (let col of otherColumns) {
if (col.constraints?.presence && !col.autocolumn) {
return true
}
}
return false
}
function checkForErrors(fromRelate, toRelate) {
const isMany =
fromRelate.relationshipType === RelationshipTypes.MANY_TO_MANY
@ -59,6 +76,10 @@
if ($touched.through && isMany && !fromRelate.through) {
errObj.through = tableNotSet
}
if ($touched.through && invalidThroughTable(fromRelate)) {
errObj.through =
"Ensure all columns in table are nullable or auto generated"
}
if ($touched.foreign && !isMany && !fromRelate.fieldName) {
errObj.foreign = "Please pick the foreign key"
}

View File

@ -1,6 +1,7 @@
<script>
import { goto } from "@roxi/routify"
import { store } from "builderStore"
import { cloneDeep } from "lodash/fp"
import { tables, datasources } from "stores/backend"
import {
ActionMenu,
@ -18,7 +19,10 @@
let editorModal
let confirmDeleteDialog
let error = ""
let originalName = table.name
let originalName
let updatedName
let templateScreens
let willBeDeleted
let deleteTableName
@ -59,7 +63,9 @@
}
async function save() {
await tables.save(table)
const updatedTable = cloneDeep(table)
updatedTable.name = updatedName
await tables.save(updatedTable)
notifications.success("Table renamed successfully")
}
@ -70,6 +76,11 @@
? `Table with name ${tableName} already exists. Please choose another name.`
: ""
}
const initForm = () => {
originalName = table.name + ""
updatedName = table.name + ""
}
</script>
{#if allowDeletion}
@ -84,17 +95,17 @@
</ActionMenu>
{/if}
<Modal bind:this={editorModal}>
<Modal bind:this={editorModal} on:show={initForm}>
<ModalContent
title="Edit Table"
confirmText="Save"
onConfirm={save}
disabled={table.name === originalName || error}
disabled={updatedName === originalName || error}
>
<Input
label="Table Name"
thin
bind:value={table.name}
bind:value={updatedName}
on:input={checkValid}
{error}
/>

View File

@ -1,6 +1,7 @@
<script>
import { goto } from "@roxi/routify"
import { views } from "stores/backend"
import { cloneDeep } from "lodash/fp"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import {
notifications,
@ -15,13 +16,17 @@
export let view
let editorModal
let originalName = view.name
let originalName
let updatedName
let confirmDeleteDialog
async function save() {
const updatedView = cloneDeep(view)
updatedView.name = updatedName
await views.save({
originalName,
...view,
...updatedView,
})
notifications.success("View renamed successfully")
}
@ -37,6 +42,11 @@
notifications.error("Error deleting view")
}
}
const initForm = () => {
updatedName = view.name + ""
originalName = view.name + ""
}
</script>
<ActionMenu>
@ -46,9 +56,9 @@
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
</ActionMenu>
<Modal bind:this={editorModal}>
<Modal bind:this={editorModal} on:show={initForm}>
<ModalContent title="Edit View" onConfirm={save} confirmText="Save">
<Input label="View Name" thin bind:value={view.name} />
<Input label="View Name" thin bind:value={updatedName} />
</ModalContent>
</Modal>
<ConfirmDialog

View File

@ -39,6 +39,7 @@
export let value = ""
export let valid
export let allowJS = false
export let allowHelpers = true
let helpers = handlebarsCompletions()
let getCaretPosition
@ -85,7 +86,7 @@
return helper.label.match(searchRgx) || helper.description.match(searchRgx)
})
$: categoryNames = [...categories.map(cat => cat[0]), "Helpers"]
$: categoryNames = getCategoryNames(categories)
$: codeMirrorHints = bindings?.map(x => `$("${x.readableBinding}")`)
@ -96,6 +97,14 @@
}
}
const getCategoryNames = categories => {
let names = [...categories.map(cat => cat[0])]
if (allowHelpers) {
names.push("Helpers")
}
return names
}
// Adds a JS/HBS helper to the expression
const addHelper = (helper, js) => {
let tempVal
@ -343,7 +352,7 @@
for more details.
</p>
{/if}
{#if $admin.isDev}
{#if $admin.isDev && allowJS}
<div class="convert">
<Button secondary on:click={convert}>Convert to JS</Button>
</div>

View File

@ -5,6 +5,7 @@
export let valid
export let value = ""
export let allowJS = false
export let allowHelpers = true
$: enrichedBindings = enrichBindings(bindings)
@ -25,5 +26,6 @@
bindings={enrichedBindings}
{value}
{allowJS}
{allowHelpers}
on:change
/>

View File

@ -17,6 +17,7 @@
export let disabled = false
export let fillWidth
export let allowJS = true
export let allowHelpers = true
export let updateOnChange = true
export let drawerLeft
@ -77,6 +78,7 @@
on:change={event => (tempValue = event.detail)}
{bindings}
{allowJS}
{allowHelpers}
/>
</Drawer>

View File

@ -24,7 +24,7 @@ export function addJSBinding(value, caretPos, binding, { helper } = {}) {
if (!helper) {
binding = `$("${binding}")`
} else {
binding = `helper.${binding}()`
binding = `helpers.${binding}()`
}
if (caretPos.start) {
value =

View File

@ -27,6 +27,11 @@
/>
<Label small />
<Checkbox
text="Do not display default notification"
bind:value={parameters.notificationOverride}
/>
<br />
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
{#if parameters.confirm}

View File

@ -95,6 +95,11 @@
/>
<Label small />
<Checkbox
text="Do not display default notification"
bind:value={parameters.notificationOverride}
/>
<br />
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
{#if parameters.confirm}

View File

@ -48,7 +48,11 @@
getOptionLabel={query => query.name}
getOptionValue={query => query._id}
/>
<Checkbox
text="Do not display default notification"
bind:value={parameters.notificationOverride}
/>
<br />
{#if parameters.queryId}
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />

View File

@ -95,6 +95,11 @@
/>
<Label small />
<Checkbox
text="Do not display default notification"
bind:value={parameters.notificationOverride}
/>
<br />
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
{#if parameters.confirm}

View File

@ -93,6 +93,11 @@
{/if}
<Label small />
<Checkbox
text="Do not display default notification"
bind:value={parameters.notificationOverride}
/>
<br />
<Checkbox text="Require confirmation" bind:value={parameters.confirm} />
{#if parameters.confirm}

View File

@ -17,33 +17,73 @@
import { generate } from "shortid"
import { LuceneUtils, Constants } from "@budibase/frontend-core"
import { getFields } from "helpers/searchFields"
import { createEventDispatcher, onMount } from "svelte"
const dispatch = createEventDispatcher()
const { OperatorOptions } = Constants
const { getValidOperatorsForType } = LuceneUtils
import { createEventDispatcher } from "svelte"
export let schemaFields
export let filters = []
export let bindings = []
export let panel = ClientBindingPanel
export let allowBindings = true
export let allOr = false
export let fillWidth = false
export let tableId
$: dispatch("change", filters)
$: enrichedSchemaFields = getFields(schemaFields || [])
const dispatch = createEventDispatcher()
const { OperatorOptions } = Constants
const { getValidOperatorsForType } = LuceneUtils
const KeyedFieldRegex = /\d[0-9]*:/g
const behaviourOptions = [
{ value: "and", label: "Match all filters" },
{ value: "or", label: "Match any filter" },
]
let rawFilters
let matchAny = false
$: parseFilters(filters)
$: dispatch("change", enrichFilters(rawFilters, matchAny))
$: enrichedSchemaFields = getFields(
schemaFields || [],
{ allowLinks: true },
tableId
)
$: fieldOptions = enrichedSchemaFields.map(field => field.name) || []
$: valueTypeOptions = allowBindings ? ["Value", "Binding"] : ["Value"]
let behaviourValue
const behaviourOptions = [
{ value: "and", label: "Match all of the following filters" },
{ value: "or", label: "Match any of the following filters" },
]
// Remove field key prefixes and determine whether to use the "match all"
// or "match any" behaviour
const parseFilters = filters => {
matchAny = filters?.find(filter => filter.operator === "allOr") != null
rawFilters = (filters || [])
.filter(filter => filter.operator !== "allOr")
.map(filter => {
const { field } = filter
let newFilter = { ...filter }
delete newFilter.allOr
if (typeof field === "string" && field.match(KeyedFieldRegex) != null) {
const parts = field.split(":")
parts.shift()
newFilter.field = parts.join(":")
}
return newFilter
})
}
// Add field key prefixes and a special metadata filter object to indicate
// whether to use the "match all" or "match any" behaviour
const enrichFilters = (rawFilters, matchAny) => {
let count = 1
return rawFilters
.filter(filter => filter.field)
.map(filter => ({
...filter,
field: `${count++}:${filter.field}`,
}))
.concat(matchAny ? [{ operator: "allOr" }] : [])
}
const addFilter = () => {
filters = [
...filters,
rawFilters = [
...rawFilters,
{
id: generate(),
field: null,
@ -55,13 +95,13 @@
}
const removeFilter = id => {
filters = filters.filter(field => field.id !== id)
rawFilters = rawFilters.filter(field => field.id !== id)
}
const duplicateFilter = id => {
const existingFilter = filters.find(filter => filter.id === id)
const existingFilter = rawFilters.find(filter => filter.id === id)
const duplicate = { ...existingFilter, id: generate() }
filters = [...filters, duplicate]
rawFilters = [...rawFilters, duplicate]
}
const getSchema = filter => {
@ -128,32 +168,22 @@
const schema = enrichedSchemaFields.find(x => x.name === field)
return schema?.constraints?.inclusion || []
}
onMount(() => {
behaviourValue = allOr ? "or" : "and"
})
</script>
<DrawerContent>
<div class="container">
<Layout noPadding>
<Body size="S">
{#if !filters?.length}
Add your first filter expression.
{:else}
Results are filtered to only those which match all of the following
constraints.
{/if}
</Body>
{#if filters?.length}
{#if !rawFilters?.length}
<Body size="S">Add your first filter expression.</Body>
{:else}
<div class="fields">
<Select
label="Behaviour"
value={behaviourValue}
value={matchAny ? "or" : "and"}
options={behaviourOptions}
getOptionLabel={opt => opt.label}
getOptionValue={opt => opt.value}
on:change={e => (allOr = e.detail === "or")}
on:change={e => (matchAny = e.detail === "or")}
placeholder={null}
/>
</div>
@ -162,7 +192,7 @@
<Label>Filters</Label>
</div>
<div class="fields">
{#each filters as filter, idx}
{#each rawFilters as filter, idx}
<Select
bind:value={filter.field}
options={fieldOptions}
@ -264,7 +294,7 @@
column-gap: var(--spacing-l);
row-gap: var(--spacing-s);
align-items: center;
grid-template-columns: 1fr 150px 120px 1fr 16px 16px;
grid-template-columns: minmax(150px, 1fr) 170px 120px minmax(150px, 1fr) 16px 16px;
}
.filter-label {

View File

@ -8,74 +8,22 @@
import FilterDrawer from "./FilterDrawer.svelte"
import { currentAsset } from "builderStore"
const QUERY_START_REGEX = /\d[0-9]*:/g
const dispatch = createEventDispatcher()
export let value = []
export let componentInstance
export let bindings = []
let drawer,
toSaveFilters = null,
allOr,
initialAllOr
let drawer
$: initialFilters = correctFilters(value || [])
$: tempValue = value
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance)
$: schema = getSchemaForDatasource($currentAsset, dataSource)?.schema
$: schemaFields = Object.values(schema || {})
function addNumbering(filters) {
let count = 1
for (let value of filters) {
if (value.field && value.field?.match(QUERY_START_REGEX) == null) {
value.field = `${count++}:${value.field}`
}
}
return filters
}
function correctFilters(filters) {
const corrected = []
for (let filter of filters) {
let field = filter.field
if (filter.operator === "allOr") {
initialAllOr = allOr = true
continue
}
if (
typeof filter.field === "string" &&
filter.field.match(QUERY_START_REGEX) != null
) {
const parts = field.split(":")
const number = parts[0]
// it's the new format, remove number
if (!isNaN(parseInt(number))) {
parts.shift()
field = parts.join(":")
}
}
corrected.push({
...filter,
field,
})
}
return corrected
}
async function saveFilter() {
if (!toSaveFilters && allOr !== initialAllOr) {
toSaveFilters = initialFilters
}
const filters = toSaveFilters?.filter(filter => filter.operator !== "allOr")
if (allOr && filters) {
filters.push({ operator: "allOr" })
}
// only save if anything was updated
if (filters) {
dispatch("change", addNumbering(filters))
}
notifications.success("Filters saved.")
dispatch("change", tempValue)
notifications.success("Filters saved")
drawer.hide()
}
</script>
@ -85,12 +33,10 @@
<Button cta slot="buttons" on:click={saveFilter}>Save</Button>
<FilterDrawer
slot="body"
filters={initialFilters}
filters={value}
{bindings}
{schemaFields}
bind:allOr
on:change={event => {
toSaveFilters = event.detail
}}
tableId={dataSource.tableId}
on:change={e => (tempValue = e.detail)}
/>
</Drawer>

View File

@ -33,6 +33,7 @@
export let showMenu = false
export let bindings = []
export let bindingDrawerLeft
export let allowHelpers = true
let fields = Object.entries(object || {}).map(([name, value]) => ({
name,
@ -122,6 +123,7 @@
disabled={readOnly}
value={field.value}
allowJS={false}
{allowHelpers}
fillWidth={true}
drawerLeft={bindingDrawerLeft}
/>

View File

@ -223,6 +223,7 @@
.config {
display: grid;
grid-gap: var(--spacing-s);
z-index: 1;
}
.config-field {

View File

@ -37,6 +37,7 @@
valuePlaceholder="Default"
bindings={[...userBindings]}
bindingDrawerLeft="260px"
allowHelpers={false}
on:change
/>
</div>

View File

@ -16,7 +16,11 @@ export function getTableFields(linkField) {
}))
}
export function getFields(fields, { allowLinks } = { allowLinks: true }) {
export function getFields(
fields,
{ allowLinks } = { allowLinks: true },
tableId
) {
let filteredFields = fields.filter(
field => !BannedSearchTypes.includes(field.type)
)
@ -30,5 +34,9 @@ export function getFields(fields, { allowLinks } = { allowLinks: true }) {
const staticFormulaFields = fields.filter(
field => field.type === "formula" && field.formulaType === "static"
)
const table = get(tables).list.find(table => table._id === tableId)
if (table?.type === "external" && table?.sql) {
filteredFields = filteredFields.filter(field => field.name !== "_id")
}
return filteredFields.concat(staticFormulaFields)
}

View File

@ -61,6 +61,13 @@
align-items: center;
gap: var(--spacing-l);
}
.header-left {
flex: 1 1 auto;
width: 0;
}
.header-left :global(> *) {
max-width: 100%;
}
.header-left :global(.spectrum-Picker) {
font-weight: 600;
color: var(--spectrum-global-color-gray-900);

View File

@ -282,12 +282,16 @@
gap: var(--spacing-l);
display: grid;
align-items: center;
grid-template-columns: auto 160px auto 1fr 130px 130px 1fr auto auto;
grid-template-columns:
auto 150px auto minmax(140px, 1fr) 120px 100px minmax(140px, 1fr)
auto auto;
border-radius: var(--border-radius-s);
transition: background-color ease-in-out 130ms;
}
.condition.update {
grid-template-columns: auto 160px 1fr auto 1fr auto 1fr 130px 130px 1fr auto auto;
grid-template-columns:
auto 150px minmax(140px, 1fr) auto minmax(140px, 1fr) auto
minmax(140px, 1fr) 120px 100px minmax(140px, 1fr) auto auto;
}
.condition:hover {
background-color: var(--spectrum-global-color-gray-100);

View File

@ -48,7 +48,6 @@
? {
title: "User Groups",
href: "/builder/portal/manage/groups",
badge: "New",
}
: undefined,
{ title: "Auth", href: "/builder/portal/manage/auth" },
@ -56,7 +55,6 @@
{
title: "Plugins",
href: "/builder/portal/manage/plugins",
badge: "New",
},
{
@ -119,14 +117,12 @@
{
title: "Upgrade",
href: $adminStore.accountPortalUrl + "/portal/upgrade",
badge: "New",
},
])
} else if (!$adminStore.cloud && admin) {
menu = menu.concat({
title: "Upgrade",
href: "/builder/portal/settings/upgrade",
badge: "New",
})
}

View File

@ -50,7 +50,7 @@
}
</script>
<ModalContent showCancelButton={false} {title} confirmText="Done">
<ModalContent size="M" showCancelButton={false} {title} confirmText="Done">
{#if hasSuccess}
<Body size="XS">
Your users should now receive an email invite to get access to their
@ -70,6 +70,3 @@
/>
{/if}
</ModalContent>
<style>
</style>

View File

@ -7,7 +7,6 @@
Table,
Layout,
Modal,
ModalContent,
Search,
notifications,
Pagination,
@ -23,6 +22,7 @@
import { goto } from "@roxi/routify"
import OnboardingTypeModal from "./_components/OnboardingTypeModal.svelte"
import PasswordModal from "./_components/PasswordModal.svelte"
import InvitedModal from "./_components/InvitedModal.svelte"
import ImportUsersModal from "./_components/ImportUsersModal.svelte"
import { get } from "svelte/store"
import { Constants, Utils, fetchData } from "@budibase/frontend-core"
@ -67,6 +67,8 @@
sortable: false,
},
}
$: userData = []
$: inviteUsersResponse = { successful: [], unsuccessful: [] }
$: {
enrichedUsers = $fetch.rows?.map(user => {
let userGroups = []
@ -112,8 +114,7 @@
groups: userData.groups,
}))
try {
const res = await users.invite(payload)
notifications.success(res.message)
inviteUsersResponse = await users.invite(payload)
inviteConfirmationModal.show()
} catch (error) {
notifications.error("Error inviting user")
@ -273,16 +274,7 @@
</Modal>
<Modal bind:this={inviteConfirmationModal}>
<ModalContent
showCancelButton={false}
title="Invites sent!"
confirmText="Done"
>
<Body size="S">
Your users should now recieve an email invite to get access to their
Budibase account
</Body>
</ModalContent>
<InvitedModal {inviteUsersResponse} />
</Modal>
<Modal bind:this={onboardingTypeModal}>

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/cli",
"version": "2.1.40-alpha.7",
"version": "2.1.46-alpha.1",
"description": "Budibase CLI, for developers, self hosting and migrations.",
"main": "src/index.js",
"bin": {
@ -26,9 +26,9 @@
"outputPath": "build"
},
"dependencies": {
"@budibase/backend-core": "2.1.40-alpha.7",
"@budibase/string-templates": "2.1.40-alpha.7",
"@budibase/types": "2.1.40-alpha.7",
"@budibase/backend-core": "2.1.46-alpha.1",
"@budibase/string-templates": "2.1.46-alpha.1",
"@budibase/types": "2.1.46-alpha.1",
"axios": "0.21.2",
"chalk": "4.1.0",
"cli-progress": "3.11.2",

File diff suppressed because it is too large Load Diff

View File

@ -2598,6 +2598,7 @@
]
},
"passwordfield": {
"skeleton": false,
"name": "Password Field",
"icon": "LockClosed",
"styles": [
@ -3066,6 +3067,7 @@
]
},
"longformfield": {
"skeleton": false,
"name": "Long Form Field",
"icon": "TextAlignLeft",
"styles": [
@ -5118,7 +5120,8 @@
{
"type": "multifield",
"label": "Fields",
"key": "fields"
"key": "fields",
"selectAllFields": true
},
{
"type": "select",
@ -5204,7 +5207,8 @@
"icon": "RailRight",
"hasChildren": true,
"illegalChildren": [
"section"
"section",
"sidepanel"
],
"showEmptyState": false,
"draggable": false,
@ -5283,7 +5287,8 @@
"type": "multifield",
"label": "Fields",
"key": "detailFields",
"nested": true
"nested": true,
"selectAllFields": true
}
]
}
@ -5293,4 +5298,4 @@
"suffix": "repeater"
}
}
}
}

View File

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

View File

@ -479,6 +479,7 @@
definition.name !== "Screenslot" &&
children.length === 0 &&
!instance._blockElementHasChildren &&
!definition.block &&
definition.skeleton !== false
</script>

View File

@ -29,6 +29,17 @@
// Derive visibility
$: open = $sidePanelStore.contentId === $component.id
// Derive a render key which is only changed whenever this panel is made
// visible after being hidden. We need to key the content to avoid showing
// stale data when re-revealing a side panel that was closed, but we cannot
// hide the content altogether when hidden as this breaks ejection.
let renderKey = null
$: {
if (open) {
renderKey = Math.random()
}
}
const showInSidePanel = (el, visible) => {
const update = visible => {
const target = document.getElementById("side-panel-container")
@ -47,7 +58,10 @@
// Apply initial visibility
update(visible)
return { update }
return {
update,
destroy: () => update(false),
}
}
</script>
@ -57,7 +71,9 @@
class="side-panel"
class:open
>
<slot />
{#key renderKey}
<slot />
{/key}
</div>
<style>

View File

@ -36,7 +36,6 @@
let dataProviderId
let repeaterId
let schema
let schemaLoaded = false
$: fetchSchema(dataSource)
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
@ -75,138 +74,135 @@
enrichRelationships: true,
})
}
schemaLoaded = true
}
</script>
{#if schemaLoaded}
<Block>
<BlockComponent
type="form"
bind:id={formId}
props={{ dataSource, disableValidation: true }}
>
{#if title || enrichedSearchColumns?.length || showTitleButton}
<Block>
<BlockComponent
type="form"
bind:id={formId}
props={{ dataSource, disableValidation: true }}
>
{#if title || enrichedSearchColumns?.length || showTitleButton}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "middle",
gap: "M",
wrap: true,
}}
styles={{
normal: {
"margin-bottom": "20px",
},
}}
order={0}
>
<BlockComponent
type="heading"
props={{
text: title,
}}
order={0}
/>
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
hAlign: "left",
vAlign: "middle",
gap: "M",
wrap: true,
}}
order={1}
>
{#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx}
<BlockComponent
type={column.componentType}
props={{
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
order={idx}
styles={{
normal: {
width: "192px",
},
}}
/>
{/each}
{/if}
{#if showTitleButton}
<BlockComponent
type="button"
props={{
onClick: titleButtonAction,
text: titleButtonText,
type: "cta",
}}
order={enrichedSearchColumns?.length ?? 0}
/>
{/if}
</BlockComponent>
</BlockComponent>
{/if}
<BlockComponent
type="dataprovider"
bind:id={dataProviderId}
props={{
dataSource,
filter: enrichedFilter,
sortColumn,
sortOrder,
paginate,
limit,
}}
order={1}
>
<BlockComponent
type="repeater"
bind:id={repeaterId}
context="repeater"
props={{
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
direction: "row",
hAlign: "stretch",
vAlign: "top",
gap: "M",
noRowsMessage: "No rows found",
}}
styles={{
custom: `display: grid;\ngrid-template-columns: repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr));`,
}}
order={0}
>
<BlockComponent
type="spectrumcard"
props={{
title: cardTitle,
subtitle: cardSubtitle,
description: cardDescription,
imageURL: cardImageURL,
horizontal: cardHorizontal,
showButton: showCardButton,
buttonText: cardButtonText,
buttonOnClick: cardButtonOnClick,
linkURL: fullCardURL,
linkPeek: cardPeek,
}}
styles={{
normal: {
"margin-bottom": "20px",
width: "auto",
},
}}
order={0}
>
<BlockComponent
type="heading"
props={{
text: title,
}}
order={0}
/>
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "left",
vAlign: "middle",
gap: "M",
wrap: true,
}}
order={1}
>
{#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx}
<BlockComponent
type={column.componentType}
props={{
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
order={idx}
styles={{
normal: {
width: "192px",
},
}}
/>
{/each}
{/if}
{#if showTitleButton}
<BlockComponent
type="button"
props={{
onClick: titleButtonAction,
text: titleButtonText,
type: "cta",
}}
order={enrichedSearchColumns?.length ?? 0}
/>
{/if}
</BlockComponent>
</BlockComponent>
{/if}
<BlockComponent
type="dataprovider"
bind:id={dataProviderId}
props={{
dataSource,
filter: enrichedFilter,
sortColumn,
sortOrder,
paginate,
limit,
}}
order={1}
>
<BlockComponent
type="repeater"
bind:id={repeaterId}
context="repeater"
props={{
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
direction: "row",
hAlign: "stretch",
vAlign: "top",
gap: "M",
noRowsMessage: "No rows found",
}}
styles={{
custom: `display: grid;\ngrid-template-columns: repeat(auto-fill, minmax(min(${cardWidth}px, 100%), 1fr));`,
}}
order={0}
>
<BlockComponent
type="spectrumcard"
props={{
title: cardTitle,
subtitle: cardSubtitle,
description: cardDescription,
imageURL: cardImageURL,
horizontal: cardHorizontal,
showButton: showCardButton,
buttonText: cardButtonText,
buttonOnClick: cardButtonOnClick,
linkURL: fullCardURL,
linkPeek: cardPeek,
}}
styles={{
normal: {
width: "auto",
},
}}
order={0}
/>
</BlockComponent>
/>
</BlockComponent>
</BlockComponent>
</Block>
{/if}
</BlockComponent>
</Block>

View File

@ -36,7 +36,6 @@
let newRowSidePanelId
let schema
let primaryDisplay
let schemaLoaded = false
$: fetchSchema(dataSource)
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema)
@ -66,7 +65,7 @@
},
]
$: buttonClickActions =
clickBehaviour === "actions" || dataSource?.type !== "table"
titleButtonClickBehaviour === "actions" || dataSource?.type !== "table"
? onClickTitleButton
: [
{
@ -89,7 +88,6 @@
enrichRelationships: true,
})
}
schemaLoaded = true
}
const getNormalFields = schema => {
@ -113,162 +111,160 @@
}
</script>
{#if schemaLoaded}
<Block>
<BlockComponent
type="form"
bind:id={formId}
props={{
dataSource,
disableValidation: true,
editAutoColumns: true,
size,
}}
>
{#if title || enrichedSearchColumns?.length || showTitleButton}
<Block>
<BlockComponent
type="form"
bind:id={formId}
props={{
dataSource,
disableValidation: true,
editAutoColumns: true,
size,
}}
>
{#if title || enrichedSearchColumns?.length || showTitleButton}
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "middle",
gap: "M",
wrap: true,
}}
styles={{
normal: {
"margin-bottom": "20px",
},
}}
order={0}
>
<BlockComponent
type="heading"
props={{
text: title,
}}
order={0}
/>
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "stretch",
vAlign: "middle",
hAlign: "left",
vAlign: "center",
gap: "M",
wrap: true,
}}
styles={{
normal: {
"margin-bottom": "20px",
},
}}
order={0}
order={1}
>
<BlockComponent
type="heading"
props={{
text: title,
}}
order={0}
/>
<BlockComponent
type="container"
props={{
direction: "row",
hAlign: "left",
vAlign: "center",
gap: "M",
wrap: true,
}}
order={1}
>
{#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx}
<BlockComponent
type={column.componentType}
props={{
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
styles={{
normal: {
width: "192px",
},
}}
order={idx}
/>
{/each}
{/if}
{#if showTitleButton}
{#if enrichedSearchColumns?.length}
{#each enrichedSearchColumns as column, idx}
<BlockComponent
type="button"
type={column.componentType}
props={{
onClick: buttonClickActions,
text: titleButtonText,
type: "cta",
field: column.name,
placeholder: column.name,
text: column.name,
autoWidth: true,
}}
order={enrichedSearchColumns?.length ?? 0}
styles={{
normal: {
width: "192px",
},
}}
order={idx}
/>
{/if}
</BlockComponent>
{/each}
{/if}
{#if showTitleButton}
<BlockComponent
type="button"
props={{
onClick: buttonClickActions,
text: titleButtonText,
type: "cta",
}}
order={enrichedSearchColumns?.length ?? 0}
/>
{/if}
</BlockComponent>
{/if}
</BlockComponent>
{/if}
<BlockComponent
type="dataprovider"
bind:id={dataProviderId}
props={{
dataSource,
filter: enrichedFilter,
sortColumn: sortColumn || primaryDisplay,
sortOrder,
paginate,
limit: rowCount,
}}
order={1}
>
<BlockComponent
type="dataprovider"
bind:id={dataProviderId}
type="table"
context="table"
props={{
dataSource,
filter: enrichedFilter,
sortColumn: sortColumn || primaryDisplay,
sortOrder,
paginate,
limit: rowCount,
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
columns: tableColumns,
rowCount,
quiet,
compact,
allowSelectRows,
size,
onClick: rowClickActions,
}}
order={1}
/>
</BlockComponent>
{#if clickBehaviour === "details"}
<BlockComponent
name="Details side panel"
type="sidepanel"
bind:id={detailsSidePanelId}
context="details-side-panel"
order={2}
>
<BlockComponent
type="table"
context="table"
name="Details form block"
type="formblock"
bind:id={detailsFormBlockId}
props={{
dataProvider: `{{ literal ${safe(dataProviderId)} }}`,
columns: tableColumns,
rowCount,
quiet,
compact,
allowSelectRows,
size,
onClick: rowClickActions,
dataSource,
showSaveButton: true,
showDeleteButton: true,
actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: normalFields,
title: editTitle,
labelPosition: "left",
}}
/>
</BlockComponent>
{#if clickBehaviour === "details"}
{/if}
{#if showTitleButton && titleButtonClickBehaviour === "new"}
<BlockComponent
name="New row side panel"
type="sidepanel"
bind:id={newRowSidePanelId}
context="new-side-panel"
order={3}
>
<BlockComponent
name="Details side panel"
type="sidepanel"
bind:id={detailsSidePanelId}
context="details-side-panel"
order={2}
>
<BlockComponent
name="Details form block"
type="formblock"
bind:id={detailsFormBlockId}
props={{
dataSource,
showSaveButton: true,
showDeleteButton: true,
actionType: "Update",
rowId: `{{ ${safe("state")}.${safe(stateKey)} }}`,
fields: normalFields,
title: editTitle,
labelPosition: "left",
}}
/>
</BlockComponent>
{/if}
{#if showTitleButton && titleButtonClickBehaviour === "new"}
<BlockComponent
name="New row side panel"
type="sidepanel"
bind:id={newRowSidePanelId}
context="new-side-panel"
order={3}
>
<BlockComponent
name="New row form block"
type="formblock"
props={{
dataSource,
showSaveButton: true,
showDeleteButton: false,
actionType: "Create",
fields: normalFields,
title: "Create Row",
labelPosition: "left",
}}
/>
</BlockComponent>
{/if}
</BlockComponent>
</Block>
{/if}
name="New row form block"
type="formblock"
props={{
dataSource,
showSaveButton: true,
showDeleteButton: false,
actionType: "Create",
fields: normalFields,
title: "Create Row",
labelPosition: "left",
}}
/>
</BlockComponent>
{/if}
</BlockComponent>
</Block>

View File

@ -20,12 +20,10 @@
const context = getContext("context")
const { API, fetchDatasourceSchema } = getContext("sdk")
let loaded = false
let schema
let table
$: fetchSchema(dataSource)
$: fetchTable(dataSource)
// Returns the closes data context which isn't a built in context
const getInitialValues = (type, dataSource, context) => {
@ -48,13 +46,6 @@
// Fetches the form schema from this form's dataSource
const fetchSchema = async dataSource => {
schema = (await fetchDatasourceSchema(dataSource)) || {}
if (!loaded) {
loaded = true
}
}
const fetchTable = async dataSource => {
if (dataSource?.tableId && dataSource?.type !== "query") {
try {
table = await API.fetchTableDefinition(dataSource.tableId)
@ -62,29 +53,32 @@
table = null
}
}
const res = await fetchDatasourceSchema(dataSource)
schema = res || {}
}
$: initialValues = getInitialValues(actionType, dataSource, $context)
$: resetKey = Helpers.hashString(
JSON.stringify(initialValues) + JSON.stringify(dataSource) + disabled
!!schema +
JSON.stringify(initialValues) +
JSON.stringify(dataSource) +
disabled
)
</script>
{#if loaded}
{#key resetKey}
<InnerForm
{dataSource}
{theme}
{size}
{disabled}
{actionType}
{schema}
{table}
{initialValues}
{disableValidation}
{editAutoColumns}
>
<slot />
</InnerForm>
{/key}
{/if}
{#key resetKey}
<InnerForm
{dataSource}
{theme}
{size}
{disabled}
{actionType}
{schema}
{table}
{initialValues}
{disableValidation}
{editAutoColumns}
>
<slot />
</InnerForm>
{/key}

View File

@ -1,4 +1,5 @@
import { derived } from "svelte/store"
import { Constants } from "@budibase/frontend-core"
import { devToolsStore } from "../devTools.js"
import { authStore } from "../auth.js"
@ -6,6 +7,10 @@ import { authStore } from "../auth.js"
export const currentRole = derived(
[devToolsStore, authStore],
([$devToolsStore, $authStore]) => {
return ($devToolsStore.enabled && $devToolsStore.role) || $authStore?.roleId
return (
($devToolsStore.enabled && $devToolsStore.role) ||
$authStore?.roleId ||
Constants.Roles.PUBLIC
)
}
)

View File

@ -11,18 +11,25 @@ export const createSidePanelStore = () => {
open: $store.contentId != null,
}
})
let timeout
const open = id => {
clearTimeout(timeout)
store.update(state => {
state.contentId = id
return state
})
}
// Delay closing by 50ms to avoid toggling visibility when cycling though
// records
const close = () => {
store.update(state => {
state.contentId = null
return state
})
timeout = setTimeout(() => {
store.update(state => {
state.contentId = null
return state
})
}, 50)
}
return {

View File

@ -18,7 +18,8 @@ import { enrichDataBindings } from "./enrichDataBinding"
import { Helpers } from "@budibase/bbui"
const saveRowHandler = async (action, context) => {
const { fields, providerId, tableId } = action.parameters
const { fields, providerId, tableId, notificationOverride } =
action.parameters
let payload
if (providerId) {
payload = { ...context[providerId] }
@ -35,7 +36,10 @@ const saveRowHandler = async (action, context) => {
}
try {
const row = await API.saveRow(payload)
notificationStore.actions.success("Row saved")
if (!notificationOverride) {
notificationStore.actions.success("Row saved")
}
// Refresh related datasources
await dataSourceStore.actions.invalidateDataSource(row.tableId, {
@ -50,7 +54,8 @@ const saveRowHandler = async (action, context) => {
}
const duplicateRowHandler = async (action, context) => {
const { fields, providerId, tableId } = action.parameters
const { fields, providerId, tableId, notificationOverride } =
action.parameters
if (providerId) {
let payload = { ...context[providerId] }
if (fields) {
@ -65,7 +70,9 @@ const duplicateRowHandler = async (action, context) => {
delete payload._rev
try {
const row = await API.saveRow(payload)
notificationStore.actions.success("Row saved")
if (!notificationOverride) {
notificationStore.actions.success("Row saved")
}
// Refresh related datasources
await dataSourceStore.actions.invalidateDataSource(row.tableId, {
@ -81,11 +88,13 @@ const duplicateRowHandler = async (action, context) => {
}
const deleteRowHandler = async action => {
const { tableId, revId, rowId } = action.parameters
const { tableId, revId, rowId, notificationOverride } = action.parameters
if (tableId && rowId) {
try {
await API.deleteRow({ tableId, rowId, revId })
notificationStore.actions.success("Row deleted")
if (!notificationOverride) {
notificationStore.actions.success("Row deleted")
}
// Refresh related datasources
await dataSourceStore.actions.invalidateDataSource(tableId, {
@ -99,14 +108,16 @@ const deleteRowHandler = async action => {
}
const triggerAutomationHandler = async action => {
const { fields } = action.parameters
const { fields, notificationOverride } = action.parameters
if (fields) {
try {
await API.triggerAutomation({
automationId: action.parameters.automationId,
fields,
})
notificationStore.actions.success("Automation triggered")
if (!notificationOverride) {
notificationStore.actions.success("Automation triggered")
}
} catch (error) {
// Abort next actions
return false
@ -120,7 +131,8 @@ const navigationHandler = action => {
}
const queryExecutionHandler = async action => {
const { datasourceId, queryId, queryParams } = action.parameters
const { datasourceId, queryId, queryParams, notificationOverride } =
action.parameters
try {
const query = await API.fetchQueryDefinition(queryId)
if (query?.datasourceId == null) {
@ -136,7 +148,9 @@ const queryExecutionHandler = async action => {
// Trigger a notification and invalidate the datasource as long as this
// was not a readable query
if (!query.readable) {
notificationStore.actions.success("Query executed successfully")
if (!notificationOverride) {
notificationStore.actions.success("Query executed successfully")
}
await dataSourceStore.actions.invalidateDataSource(query.datasourceId)
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -28,11 +28,11 @@ export const OperatorOptions = {
},
MoreThan: {
value: "rangeLow",
label: "More than",
label: "More than or equal to",
},
LessThan: {
value: "rangeHigh",
label: "Less than",
label: "Less than or equal to",
},
Contains: {
value: "contains",

View File

@ -1,6 +1,6 @@
{
"name": "@budibase/sdk",
"version": "2.1.40-alpha.7",
"version": "2.1.46-alpha.1",
"description": "Budibase Public API SDK",
"author": "Budibase",
"license": "MPL-2.0",

View File

@ -1,7 +1,7 @@
{
"name": "@budibase/server",
"email": "hi@budibase.com",
"version": "2.1.40-alpha.7",
"version": "2.1.46-alpha.1",
"description": "Budibase Web Server",
"main": "src/index.ts",
"repository": {
@ -43,11 +43,11 @@
"license": "GPL-3.0",
"dependencies": {
"@apidevtools/swagger-parser": "10.0.3",
"@budibase/backend-core": "2.1.40-alpha.7",
"@budibase/client": "2.1.40-alpha.7",
"@budibase/pro": "2.1.40-alpha.7",
"@budibase/string-templates": "2.1.40-alpha.7",
"@budibase/types": "2.1.40-alpha.7",
"@budibase/backend-core": "2.1.46-alpha.1",
"@budibase/client": "2.1.46-alpha.1",
"@budibase/pro": "2.1.46-alpha.0",
"@budibase/string-templates": "2.1.46-alpha.1",
"@budibase/types": "2.1.46-alpha.1",
"@bull-board/api": "3.7.0",
"@bull-board/koa": "3.9.4",
"@elastic/elasticsearch": "7.10.0",

View File

@ -11,7 +11,7 @@ GO
CREATE TABLE products
(
id int IDENTITY(1,1),
name varchar (20),
name varchar (20) NOT NULL,
description varchar(30),
CONSTRAINT pk_products PRIMARY KEY NONCLUSTERED (id)
);
@ -22,7 +22,7 @@ GO
CREATE TABLE tasks
(
taskid int IDENTITY(1,1),
taskname varchar (20),
taskname varchar (20) NOT NULL,
productid int,
CONSTRAINT pk_tasks PRIMARY KEY NONCLUSTERED (taskid),
CONSTRAINT fk_products FOREIGN KEY (productid) REFERENCES products (id),
@ -33,7 +33,7 @@ IF OBJECT_ID ('dbo.people', 'U') IS NOT NULL
GO
CREATE TABLE people
(
name varchar(30),
name varchar(30) NOT NULL,
age varchar(20),
CONSTRAINT pk_people PRIMARY KEY NONCLUSTERED (name, age)
);

View File

@ -0,0 +1,46 @@
SELECT 'CREATE DATABASE main'
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'main')\gexec
CREATE TABLE IF NOT EXISTS public."Employee"
(
id integer NOT NULL,
name text COLLATE pg_catalog."default",
CONSTRAINT "Employee_pkey" PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
);
INSERT INTO public."Employee" ("id", "name") VALUES (1, 'Alice');
INSERT INTO public."Employee" ("id", "name") VALUES (2, 'Bob');
CREATE TABLE IF NOT EXISTS public."Skills"
(
id integer NOT NULL,
name text COLLATE pg_catalog."default",
CONSTRAINT "Skills_pkey" PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
);
INSERT INTO public."Skills" ("id", "name") VALUES (1, 'Docker');
INSERT INTO public."Skills" ("id", "name") VALUES (2, 'Microservices');
INSERT INTO public."Skills" ("id", "name") VALUES (3, 'Kubernetes');
INSERT INTO public."Skills" ("id", "name") VALUES (4, 'Spring');
CREATE TABLE IF NOT EXISTS public."jt_employee_skills_Skills_employee"
(
employee_id integer,
skills_id integer,
id integer NOT NULL,
CONSTRAINT "jt_employee_skills_Skills_employee_pkey" PRIMARY KEY (id)
)
WITH (
OIDS = FALSE
);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (1, 1, 1);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (2, 1, 2);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (3, 1, 3);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (4, 2, 2);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (5, 2, 3);
insert into public."jt_employee_skills_Skills_employee" ("id", "employee_id", "skills_id") VALUES (6, 2, 4);

View File

@ -26,7 +26,6 @@ import {
} from "@budibase/backend-core"
import { USERS_TABLE_SCHEMA } from "../../constants"
import { buildDefaultDocs } from "../../db/defaultData/datasource_bb_default"
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
import {
clientLibraryPath,
@ -39,18 +38,22 @@ import {
backupClientLibrary,
revertClientLibrary,
} from "../../utilities/fileSystem/clientLibrary"
import { syncGlobalUsers } from "./user"
import { cleanupAutomations } from "../../automations/utils"
import { checkAppMetadata } from "../../automations/logging"
import { getUniqueRows } from "../../utilities/usageQuota/rows"
import { quotas, groups } from "@budibase/pro"
import { App, Layout, Screen, MigrationType } from "@budibase/types"
import {
App,
Layout,
Screen,
MigrationType,
BBContext,
Database,
} from "@budibase/types"
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
import { enrichPluginURLs } from "../../utilities/plugins"
import sdk from "../../sdk"
const URL_REGEX_SLASH = /\/|\\/g
// utility function, need to do away with this
async function getLayouts() {
const db = context.getAppDB()
@ -74,29 +77,18 @@ async function getScreens() {
).rows.map((row: any) => row.doc)
}
function getUserRoleId(ctx: any) {
return !ctx.user.role || !ctx.user.role._id
function getUserRoleId(ctx: BBContext) {
return !ctx.user?.role || !ctx.user.role._id
? roles.BUILTIN_ROLE_IDS.PUBLIC
: ctx.user.role._id
}
export const getAppUrl = (ctx: any) => {
// construct the url
let url
if (ctx.request.body.url) {
// if the url is provided, use that
url = encodeURI(ctx.request.body.url)
} else if (ctx.request.body.name) {
// otherwise use the name
url = encodeURI(`${ctx.request.body.name}`)
}
if (url) {
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
}
return url
}
const checkAppUrl = (ctx: any, apps: any, url: any, currentAppId?: string) => {
function checkAppUrl(
ctx: BBContext,
apps: App[],
url: string,
currentAppId?: string
) {
if (currentAppId) {
apps = apps.filter((app: any) => app.appId !== currentAppId)
}
@ -105,12 +97,12 @@ const checkAppUrl = (ctx: any, apps: any, url: any, currentAppId?: string) => {
}
}
const checkAppName = (
ctx: any,
apps: any,
name: any,
function checkAppName(
ctx: BBContext,
apps: App[],
name: string,
currentAppId?: string
) => {
) {
// TODO: Replace with Joi
if (!name) {
ctx.throw(400, "Name is required")
@ -165,14 +157,14 @@ async function createInstance(template: any, includeSampleData: boolean) {
return { _id: appId }
}
const addDefaultTables = async (db: any) => {
async function addDefaultTables(db: Database) {
const defaultDbDocs = buildDefaultDocs()
// add in the default db data docs - tables, datasource, rows and links
await db.bulkDocs([...defaultDbDocs])
}
export const fetch = async (ctx: any) => {
export async function fetch(ctx: BBContext) {
const dev = ctx.query && ctx.query.status === AppStatus.DEV
const all = ctx.query && ctx.query.status === AppStatus.ALL
const apps = (await dbCore.getAllApps({ dev, all })) as App[]
@ -197,7 +189,7 @@ export const fetch = async (ctx: any) => {
ctx.body = await checkAppMetadata(apps)
}
export const fetchAppDefinition = async (ctx: any) => {
export async function fetchAppDefinition(ctx: BBContext) {
const layouts = await getLayouts()
const userRoleId = getUserRoleId(ctx)
const accessController = new roles.AccessController()
@ -212,7 +204,7 @@ export const fetchAppDefinition = async (ctx: any) => {
}
}
export const fetchAppPackage = async (ctx: any) => {
export async function fetchAppPackage(ctx: BBContext) {
const db = context.getAppDB()
let application = await db.get(DocumentType.APP_METADATA)
const layouts = await getLayouts()
@ -222,7 +214,7 @@ export const fetchAppPackage = async (ctx: any) => {
application.usedPlugins = enrichPluginURLs(application.usedPlugins)
// Only filter screens if the user is not a builder
if (!(ctx.user.builder && ctx.user.builder.global)) {
if (!(ctx.user?.builder && ctx.user.builder.global)) {
const userRoleId = getUserRoleId(ctx)
const accessController = new roles.AccessController()
screens = await accessController.checkScreensAccess(screens, userRoleId)
@ -236,11 +228,12 @@ export const fetchAppPackage = async (ctx: any) => {
}
}
const performAppCreate = async (ctx: any) => {
const apps = await dbCore.getAllApps({ dev: true })
const name = ctx.request.body.name
async function performAppCreate(ctx: BBContext) {
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
const name = ctx.request.body.name,
possibleUrl = ctx.request.body.url
checkAppName(ctx, apps, name)
const url = getAppUrl(ctx)
const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
checkAppUrl(ctx, apps, url)
const { useTemplate, templateKey, templateString } = ctx.request.body
@ -331,7 +324,7 @@ const performAppCreate = async (ctx: any) => {
return newApplication
}
const creationEvents = async (request: any, app: App) => {
async function creationEvents(request: any, app: App) {
let creationFns: ((app: App) => Promise<void>)[] = []
const body = request.body
@ -356,7 +349,7 @@ const creationEvents = async (request: any, app: App) => {
}
}
const appPostCreate = async (ctx: any, app: App) => {
async function appPostCreate(ctx: BBContext, app: App) {
const tenantId = tenancy.getTenantId()
await migrations.backPopulateMigrations({
type: MigrationType.APP,
@ -377,7 +370,7 @@ const appPostCreate = async (ctx: any, app: App) => {
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
// this import resulted in row usage exceeding the quota
// delete the app
// skip pre and post steps as no rows have been added to quotas yet
// skip pre- and post-steps as no rows have been added to quotas yet
ctx.params.appId = app.appId
await destroyApp(ctx)
}
@ -387,7 +380,7 @@ const appPostCreate = async (ctx: any, app: App) => {
}
}
export const create = async (ctx: any) => {
export async function create(ctx: BBContext) {
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
await appPostCreate(ctx, newApplication)
await cache.bustCache(cache.CacheKey.CHECKLIST)
@ -397,14 +390,15 @@ export const create = async (ctx: any) => {
// This endpoint currently operates as a PATCH rather than a PUT
// Thus name and url fields are handled only if present
export const update = async (ctx: any) => {
const apps = await dbCore.getAllApps({ dev: true })
export async function update(ctx: BBContext) {
const apps = (await dbCore.getAllApps({ dev: true })) as App[]
// validation
const name = ctx.request.body.name
const name = ctx.request.body.name,
possibleUrl = ctx.request.body.url
if (name) {
checkAppName(ctx, apps, name, ctx.params.appId)
}
const url = getAppUrl(ctx)
const url = sdk.applications.getAppUrl({ name, url: possibleUrl })
if (url) {
checkAppUrl(ctx, apps, url, ctx.params.appId)
ctx.request.body.url = url
@ -416,7 +410,7 @@ export const update = async (ctx: any) => {
ctx.body = app
}
export const updateClient = async (ctx: any) => {
export async function updateClient(ctx: BBContext) {
// Get current app version
const db = context.getAppDB()
const application = await db.get(DocumentType.APP_METADATA)
@ -440,7 +434,7 @@ export const updateClient = async (ctx: any) => {
ctx.body = app
}
export const revertClient = async (ctx: any) => {
export async function revertClient(ctx: BBContext) {
// Check app can be reverted
const db = context.getAppDB()
const application = await db.get(DocumentType.APP_METADATA)
@ -466,12 +460,15 @@ export const revertClient = async (ctx: any) => {
ctx.body = app
}
const destroyApp = async (ctx: any) => {
async function destroyApp(ctx: BBContext) {
let appId = ctx.params.appId
let isUnpublish = ctx.query && ctx.query.unpublish
if (isUnpublish) {
appId = dbCore.getProdAppID(appId)
const devAppId = dbCore.getDevAppID(appId)
// sync before removing the published app
await sdk.applications.syncApp(devAppId)
}
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
@ -501,12 +498,12 @@ const destroyApp = async (ctx: any) => {
return result
}
const preDestroyApp = async (ctx: any) => {
async function preDestroyApp(ctx: BBContext) {
const { rows } = await getUniqueRows([ctx.params.appId])
ctx.rowCount = rows.length
}
const postDestroyApp = async (ctx: any) => {
async function postDestroyApp(ctx: BBContext) {
const rowCount = ctx.rowCount
await groups.cleanupApp(ctx.params.appId)
if (rowCount) {
@ -514,7 +511,7 @@ const postDestroyApp = async (ctx: any) => {
}
}
export const destroy = async (ctx: any) => {
export async function destroy(ctx: BBContext) {
await preDestroyApp(ctx)
const result = await destroyApp(ctx)
await postDestroyApp(ctx)
@ -522,62 +519,16 @@ export const destroy = async (ctx: any) => {
ctx.body = result
}
export const sync = async (ctx: any, next: any) => {
if (env.DISABLE_AUTO_PROD_APP_SYNC) {
ctx.status = 200
ctx.body = {
message:
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
}
return next()
}
export async function sync(ctx: BBContext) {
const appId = ctx.params.appId
if (!dbCore.isDevAppID(appId)) {
ctx.throw(400, "This action cannot be performed for production apps")
}
// replicate prod to dev
const prodAppId = dbCore.getProdAppID(appId)
// specific case, want to make sure setup is skipped
const prodDb = context.getProdAppDB({ skip_setup: true })
const exists = await prodDb.exists()
if (!exists) {
// the database doesn't exist. Don't replicate
ctx.status = 200
ctx.body = {
message: "App sync not required, app not deployed.",
}
return next()
}
const replication = new dbCore.Replication({
source: prodAppId,
target: appId,
})
let error
try {
await replication.replicate(replication.appReplicateOpts())
} catch (err) {
error = err
} finally {
await replication.close()
}
// sync the users
await syncGlobalUsers()
if (error) {
ctx.throw(400, error)
} else {
ctx.body = {
message: "App sync completed successfully.",
}
ctx.body = await sdk.applications.syncApp(appId)
} catch (err: any) {
ctx.throw(err.status || 400, err.message)
}
}
export const updateAppPackage = async (appPackage: any, appId: any) => {
export async function updateAppPackage(appPackage: any, appId: any) {
return context.doInAppContext(appId, async () => {
const db = context.getAppDB()
const application = await db.get(DocumentType.APP_METADATA)
@ -598,7 +549,7 @@ export const updateAppPackage = async (appPackage: any, appId: any) => {
})
}
const migrateAppNavigation = async () => {
async function migrateAppNavigation() {
const db = context.getAppDB()
const existing: App = await db.get(DocumentType.APP_METADATA)
const layouts: Layout[] = await getLayouts()

View File

@ -22,6 +22,7 @@ async function createApp(appName: string, appDirectory: string) {
},
},
}
// @ts-ignore
return create(ctx)
}

View File

@ -8,6 +8,7 @@ import {
} from "../../../automations/utils"
import { backups } from "@budibase/pro"
import { AppBackupTrigger } from "@budibase/types"
import sdk from "../../../sdk"
// the max time we can wait for an invalidation to complete before considering it failed
const MAX_PENDING_TIME_MS = 30 * 60000
@ -86,6 +87,11 @@ async function initDeployedApp(prodAppId: any) {
}
await Promise.all(promises)
console.log("Enabled cron triggers for deployed app..")
// sync the automations back to the dev DB - since there is now cron
// information attached
await sdk.applications.syncApp(dbCore.getDevAppID(prodAppId), {
automationOnly: true,
})
}
async function deployApp(deployment: any, userId: string) {

View File

@ -5,9 +5,18 @@ import {
saveGlobalUser,
} from "../../../utilities/workerRequests"
import { publicApiUserFix } from "../../../utilities/users"
import { db as dbCore } from "@budibase/backend-core"
import { search as stringSearch } from "./utils"
import { BBContext, User } from "@budibase/types"
function getUser(ctx: any, userId?: string) {
function isLoggedInUser(ctx: BBContext, user: User) {
const loggedInId = ctx.user?._id
const globalUserId = dbCore.getGlobalIDFromUserMetadataID(loggedInId!)
// check both just incase
return globalUserId === user._id || loggedInId === user._id
}
function getUser(ctx: BBContext, userId?: string) {
if (userId) {
ctx.params = { userId }
} else if (!ctx.params?.userId) {
@ -16,37 +25,47 @@ function getUser(ctx: any, userId?: string) {
return readGlobalUser(ctx)
}
export async function search(ctx: any, next: any) {
export async function search(ctx: BBContext, next: any) {
const { name } = ctx.request.body
const users = await allGlobalUsers(ctx)
ctx.body = stringSearch(users, name, "email")
await next()
}
export async function create(ctx: any, next: any) {
export async function create(ctx: BBContext, next: any) {
const response = await saveGlobalUser(publicApiUserFix(ctx))
ctx.body = await getUser(ctx, response._id)
await next()
}
export async function read(ctx: any, next: any) {
export async function read(ctx: BBContext, next: any) {
ctx.body = await readGlobalUser(ctx)
await next()
}
export async function update(ctx: any, next: any) {
export async function update(ctx: BBContext, next: any) {
const user = await readGlobalUser(ctx)
ctx.request.body = {
...ctx.request.body,
_rev: user._rev,
}
// disallow updating your own role - always overwrite with DB roles
if (isLoggedInUser(ctx, user)) {
ctx.request.body.builder = user.builder
ctx.request.body.admin = user.admin
ctx.request.body.roles = user.roles
}
const response = await saveGlobalUser(publicApiUserFix(ctx))
ctx.body = await getUser(ctx, response._id)
await next()
}
export async function destroy(ctx: any, next: any) {
export async function destroy(ctx: BBContext, next: any) {
const user = await getUser(ctx)
// disallow deleting yourself
if (isLoggedInUser(ctx, user)) {
ctx.throw(405, "Cannot delete user using its own API key.")
}
await deleteGlobalUser(ctx)
ctx.body = user
await next()

View File

@ -588,7 +588,10 @@ export class ExternalRequest {
for (let [colName, { isMany, rows, tableId }] of Object.entries(related)) {
const table: Table | undefined = this.getTable(tableId)
// if its not the foreign key skip it, nothing to do
if (!table || (table.primary && table.primary.indexOf(colName) !== -1)) {
if (
!table ||
(!isMany && table.primary && table.primary.indexOf(colName) !== -1)
) {
continue
}
for (let row of rows) {

View File

@ -1,12 +1,7 @@
import {
generateUserMetadataID,
getUserMetadataParams,
generateUserFlagID,
} from "../../db/utils"
import { generateUserMetadataID, generateUserFlagID } from "../../db/utils"
import { InternalTables } from "../../db/utils"
import { getGlobalUsers, getRawGlobalUser } from "../../utilities/global"
import { getFullUser } from "../../utilities/users"
import { isEqual } from "lodash"
import {
context,
constants,
@ -14,59 +9,7 @@ import {
db as dbCore,
} from "@budibase/backend-core"
import { BBContext, User } from "@budibase/types"
async function rawMetadata() {
const db = context.getAppDB()
return (
await db.allDocs(
getUserMetadataParams(null, {
include_docs: true,
})
)
).rows.map(row => row.doc)
}
function combineMetadataAndUser(user: any, metadata: any) {
// skip users with no access
if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
return null
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
}
if (found == null || !isEqual(newDoc, found)) {
return {
...found,
...newDoc,
}
}
return null
}
export async function syncGlobalUsers() {
// sync user metadata
const db = context.getAppDB()
const [users, metadata] = await Promise.all([getGlobalUsers(), rawMetadata()])
const toWrite = []
for (let user of users) {
const combined = await combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}
}
await db.bulkDocs(toWrite)
}
import sdk from "../../sdk"
export async function syncUser(ctx: BBContext) {
let deleting = false,
@ -123,7 +66,7 @@ export async function syncUser(ctx: BBContext) {
metadata.roleId = roleId
}
let combined = !deleting
? combineMetadataAndUser(user, metadata)
? sdk.users.combineMetadataAndUser(user, metadata)
: {
...metadata,
status: constants.UserStatus.INACTIVE,
@ -143,7 +86,7 @@ export async function syncUser(ctx: BBContext) {
export async function fetchMetadata(ctx: BBContext) {
const global = await getGlobalUsers()
const metadata = await rawMetadata()
const metadata = await sdk.users.rawUserMetadata()
const users = []
for (let user of global) {
// find the metadata that matches up to the global ID

View File

@ -1,41 +1,23 @@
const jestOpenAPI = require("jest-openapi").default
const generateSchema = require("../../../../../specs/generate")
const setup = require("../../tests/utilities")
const { checkSlashesInUrl } = require("../../../../utilities")
const { generateMakeRequest } = require("./utils")
const yamlPath = generateSchema()
jestOpenAPI(yamlPath)
let request = setup.getRequest()
let config = setup.getConfig()
let apiKey, table, app
let apiKey, table, app, makeRequest
beforeAll(async () => {
app = await config.init()
table = await config.updateTable()
apiKey = await config.generateApiKey()
makeRequest = generateMakeRequest(apiKey, setup)
})
afterAll(setup.afterAll)
async function makeRequest(method, endpoint, body, appId = config.getAppId()) {
const extraHeaders = {
"x-budibase-api-key": apiKey,
}
if (appId) {
extraHeaders["x-budibase-app-id"] = appId
}
const req = request
[method](checkSlashesInUrl(`/api/public/v1/${endpoint}`))
.set(config.defaultHeaders(extraHeaders))
if (body) {
req.send(body)
}
const res = await req
expect(res.body).toBeDefined()
return res
}
describe("check the applications endpoints", () => {
it("should allow retrieving applications through search", async () => {
const res = await makeRequest("post", "/applications/search")

View File

@ -0,0 +1,38 @@
const setup = require("../../tests/utilities")
const { generateMakeRequest } = require("./utils")
const workerRequests = require("../../../../utilities/workerRequests")
let config = setup.getConfig()
let apiKey, globalUser, makeRequest
beforeAll(async () => {
await config.init()
globalUser = await config.globalUser()
apiKey = await config.generateApiKey(globalUser._id)
makeRequest = generateMakeRequest(apiKey, setup)
workerRequests.readGlobalUser.mockReturnValue(globalUser)
})
afterAll(setup.afterAll)
describe("check user endpoints", () => {
it("should not allow a user to update their own roles", async () => {
const res = await makeRequest("put", `/users/${globalUser._id}`, {
...globalUser,
roles: {
"app_1": "ADMIN",
}
})
expect(workerRequests.saveGlobalUser.mock.lastCall[0].body.data.roles["app_1"]).toBeUndefined()
expect(res.status).toBe(200)
expect(res.body.data.roles["app_1"]).toBeUndefined()
})
it("should not allow a user to delete themselves", async () => {
const res = await makeRequest("delete", `/users/${globalUser._id}`)
expect(res.status).toBe(405)
expect(workerRequests.deleteGlobalUser.mock.lastCall).toBeUndefined()
})
})

View File

@ -0,0 +1,28 @@
import { checkSlashesInUrl } from "../../../../utilities"
export function generateMakeRequest(apiKey: string, setup: any) {
const request = setup.getRequest()
const config = setup.getConfig()
return async (
method: string,
endpoint: string,
body?: any,
intAppId: string = config.getAppId()
) => {
const extraHeaders: any = {
"x-budibase-api-key": apiKey,
}
if (intAppId) {
extraHeaders["x-budibase-app-id"] = intAppId
}
const req = request[method](
checkSlashesInUrl(`/api/public/v1/${endpoint}`)
).set(config.defaultHeaders(extraHeaders))
if (body) {
req.send(body)
}
const res = await req
expect(res.body).toBeDefined()
return res
}
}

View File

@ -17,7 +17,6 @@ const {
checkBuilderEndpoint,
} = require("./utilities/TestFunctions")
const setup = require("./utilities")
const { basicScreen, basicLayout } = setup.structures
const { AppStatus } = require("../../../db/utils")
const { events } = require("@budibase/backend-core")

View File

@ -0,0 +1,134 @@
export interface MSSQLTablesResponse {
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
TABLE_TYPE: string
}
export interface MSSQLColumn {
IS_COMPUTED: number
IS_IDENTITY: number
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
COLUMN_NAME: string
ORDINAL_POSITION: number
COLUMN_DEFAULT: null | any
IS_NULLABLE: "NO" | "YES"
DATA_TYPE: string
CHARACTER_MAXIMUM_LENGTH: null | number
CHARACTER_OCTET_LENGTH: null | number
NUMERIC_PRECISION: null | number
NUMERIC_PRECISION_RADIX: null | number
NUMERIC_SCALE: null | number
DATETIME_PRECISION: null | string
CHARACTER_SET_CATALOG: null | string
CHARACTER_SET_SCHEMA: null | string
CHARACTER_SET_NAME: null | string
COLLATION_CATALOG: null | string
COLLATION_SCHEMA: null | string
COLLATION_NAME: null | string
DOMAIN_CATALOG: null | string
DOMAIN_SCHEMA: null | string
DOMAIN_NAME: null | string
}
export interface PostgresColumn {
table_catalog: string
table_schema: string
table_name: string
column_name: string
ordinal_position: number
column_default: null | any
is_nullable: "NO" | "YES"
data_type: string
character_maximum_length: null | number
character_octet_length: null | number
numeric_precision: null | number
numeric_precision_radix: null | number
numeric_scale: null | number
datetime_precision: null | string
interval_type: null | string
interval_precision: null | string
character_set_catalog: null | string
character_set_schema: null | string
character_set_name: null | string
collation_catalog: null | string
collation_schema: null | string
collation_name: null | string
domain_catalog: null | string
domain_schema: null | string
domain_name: null | string
udt_catalog: string
udt_schema: string
udt_name: string
scope_catalog: null | string
scope_schema: null | string
scope_name: null | string
maximum_cardinality: null | string
dtd_identifier: string
is_self_referencing: "NO" | "YES"
is_identity: "NO" | "YES"
identity_generation: null | number
identity_start: null | number
identity_increment: null | number
identity_maximum: null | number
identity_minimum: null | number
identity_cycle: "NO" | "YES"
is_generated: "NEVER"
generation_expression: null | string
is_updatable: "NO" | "YES"
}
export interface MySQLColumn {
Field: string
Type: string
Null: "NO" | "YES"
Key: "PRI" | "MUL" | ""
Default: null | any
Extra: null | string
}
/**
* Raw query response
*/
export interface OracleColumnsResponse {
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_DEFAULT: null | string
COLUMN_ID: number
CONSTRAINT_NAME: null | string
CONSTRAINT_TYPE: null | string
R_CONSTRAINT_NAME: null | string
SEARCH_CONDITION: null | string
}
/**
* An oracle constraint
*/
export interface OracleConstraint {
name: string
type: string
relatedConstraintName: null | string
searchCondition: null | string
}
/**
* An oracle column and it's related constraints
*/
export interface OracleColumn {
name: string
type: string
default: null | string
id: number
constraints: { [key: string]: OracleConstraint }
}
/**
* An oracle table and it's related columns
*/
export interface OracleTable {
name: string
columns: { [key: string]: OracleColumn }
}

View File

@ -17,6 +17,7 @@ import {
SqlClient,
} from "./utils"
import Sql from "./base/sql"
import { MSSQLTablesResponse, MSSQLColumn } from "./base/types"
const sqlServer = require("mssql")
const DEFAULT_SCHEMA = "dbo"
@ -31,13 +32,6 @@ interface MSSQLConfig {
encrypt?: boolean
}
interface TablesResponse {
TABLE_CATALOG: string
TABLE_SCHEMA: string
TABLE_NAME: string
TABLE_TYPE: string
}
const SCHEMA: Integration = {
docs: "https://github.com/tediousjs/node-mssql",
plus: true,
@ -210,7 +204,7 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
*/
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
await this.connect()
let tableInfo: TablesResponse[] = await this.runSQL(this.TABLES_SQL)
let tableInfo: MSSQLTablesResponse[] = await this.runSQL(this.TABLES_SQL)
if (tableInfo == null || !Array.isArray(tableInfo)) {
throw "Unable to get list of tables in database"
}
@ -228,15 +222,20 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
// find primary key constraints
const constraints = await this.runSQL(this.getConstraintsSQL(tableName))
// find the computed and identity columns (auto columns)
const columns = await this.runSQL(this.getAutoColumnsSQL(tableName))
const columns: MSSQLColumn[] = await this.runSQL(
this.getAutoColumnsSQL(tableName)
)
const primaryKeys = constraints
.filter(
(constraint: any) => constraint.CONSTRAINT_TYPE === "PRIMARY KEY"
)
.map((constraint: any) => constraint.COLUMN_NAME)
const autoColumns = columns
.filter((col: any) => col.IS_COMPUTED || col.IS_IDENTITY)
.map((col: any) => col.COLUMN_NAME)
.filter(col => col.IS_COMPUTED || col.IS_IDENTITY)
.map(col => col.COLUMN_NAME)
const requiredColumns = columns
.filter(col => col.IS_NULLABLE === "NO")
.map(col => col.COLUMN_NAME)
let schema: TableSchema = {}
for (let def of definition) {
@ -245,8 +244,11 @@ class SqlServerIntegration extends Sql implements DatasourcePlus {
continue
}
schema[name] = {
autocolumn: !!autoColumns.find((col: string) => col === name),
autocolumn: !!autoColumns.find(col => col === name),
name: name,
constraints: {
presence: requiredColumns.find(col => col === name),
},
...convertSqlType(def.DATA_TYPE),
externalType: def.DATA_TYPE,
}

View File

@ -18,6 +18,7 @@ import {
import dayjs from "dayjs"
const { NUMBER_REGEX } = require("../utilities")
import Sql from "./base/sql"
import { MySQLColumn } from "./base/types"
const mysql = require("mysql2/promise")
@ -203,11 +204,11 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
try {
// get the tables first
const tablesResp = await this.internalQuery(
const tablesResp: Record<string, string>[] = await this.internalQuery(
{ sql: "SHOW TABLES;" },
{ connect: false }
)
const tableNames = tablesResp.map(
const tableNames: string[] = tablesResp.map(
(obj: any) =>
obj[`Tables_in_${database}`] ||
obj[`Tables_in_${database.toLowerCase()}`]
@ -215,7 +216,7 @@ class MySQLIntegration extends Sql implements DatasourcePlus {
for (let tableName of tableNames) {
const primaryKeys = []
const schema: TableSchema = {}
const descResp = await this.internalQuery(
const descResp: MySQLColumn[] = await this.internalQuery(
{ sql: `DESCRIBE \`${tableName}\`;` },
{ connect: false }
)

View File

@ -24,6 +24,12 @@ import {
ExecuteOptions,
Result,
} from "oracledb"
import {
OracleTable,
OracleColumn,
OracleColumnsResponse,
OracleConstraint,
} from "./base/types"
let oracledb: any
try {
oracledb = require("oracledb")
@ -89,50 +95,6 @@ const SCHEMA: Integration = {
const UNSUPPORTED_TYPES = ["BLOB", "CLOB", "NCLOB"]
/**
* Raw query response
*/
interface ColumnsResponse {
TABLE_NAME: string
COLUMN_NAME: string
DATA_TYPE: string
DATA_DEFAULT: string | null
COLUMN_ID: number
CONSTRAINT_NAME: string | null
CONSTRAINT_TYPE: string | null
R_CONSTRAINT_NAME: string | null
SEARCH_CONDITION: string | null
}
/**
* An oracle constraint
*/
interface OracleConstraint {
name: string
type: string
relatedConstraintName: string | null
searchCondition: string | null
}
/**
* An oracle column and it's related constraints
*/
interface OracleColumn {
name: string
type: string
default: string | null
id: number
constraints: { [key: string]: OracleConstraint }
}
/**
* An oracle table and it's related columns
*/
interface OracleTable {
name: string
columns: { [key: string]: OracleColumn }
}
const OracleContraintTypes = {
PRIMARY: "P",
NOT_NULL_OR_CHECK: "C",
@ -195,7 +157,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
/**
* Map the flat tabular columns and constraints data into a nested object
*/
private mapColumns(result: Result<ColumnsResponse>): {
private mapColumns(result: Result<OracleColumnsResponse>): {
[key: string]: OracleTable
} {
const oracleTables: { [key: string]: OracleTable } = {}
@ -299,7 +261,7 @@ class OracleIntegration extends Sql implements DatasourcePlus {
* @param entities - the tables that are to be built
*/
async buildSchema(datasourceId: string, entities: Record<string, Table>) {
const columnsResponse = await this.internalQuery<ColumnsResponse>({
const columnsResponse = await this.internalQuery<OracleColumnsResponse>({
sql: this.COLUMNS_SQL,
})
const oracleTables = this.mapColumns(columnsResponse)
@ -334,6 +296,9 @@ class OracleIntegration extends Sql implements DatasourcePlus {
fieldSchema = {
autocolumn: OracleIntegration.isAutoColumn(oracleColumn),
name: columnName,
constraints: {
presence: false,
},
...this.internalConvertType(oracleColumn),
}
table.schema[columnName] = fieldSchema
@ -343,6 +308,12 @@ class OracleIntegration extends Sql implements DatasourcePlus {
Object.values(oracleColumn.constraints).forEach(oracleConstraint => {
if (oracleConstraint.type === OracleContraintTypes.PRIMARY) {
table.primary!.push(columnName)
} else if (
oracleConstraint.type === OracleContraintTypes.NOT_NULL_OR_CHECK
) {
table.schema[columnName].constraints = {
presence: true,
}
}
})
})

View File

@ -15,9 +15,10 @@ import {
SqlClient,
} from "./utils"
import Sql from "./base/sql"
import { PostgresColumn } from "./base/types"
import { escapeDangerousCharacters } from "../utilities"
const { Client, types } = require("pg")
const { escapeDangerousCharacters } = require("../utilities")
// Return "date" and "timestamp" types as plain strings.
// This lets us reference the original stored timezone.
@ -237,7 +238,8 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
}
try {
const columnsResponse = await this.client.query(this.COLUMNS_SQL)
const columnsResponse: { rows: PostgresColumn[] } =
await this.client.query(this.COLUMNS_SQL)
const tables: { [key: string]: Table } = {}
@ -260,6 +262,9 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
column.identity_start ||
column.identity_increment
)
const constraints = {
presence: column.is_nullable === "NO",
}
const hasDefault =
typeof column.column_default === "string" &&
column.column_default.startsWith("nextval")
@ -269,6 +274,7 @@ class PostgresIntegration extends Sql implements DatasourcePlus {
tables[tableName].schema[columnName] = {
autocolumn: isAuto,
name: columnName,
constraints,
...convertSqlType(column.data_type),
externalType: column.data_type,
}

View File

@ -1,5 +1,5 @@
import { db as dbCore } from "@budibase/backend-core"
import { getAppUrl } from "../../api/controllers/application"
import sdk from "../../sdk"
/**
* Date:
@ -20,14 +20,7 @@ export const run = async (appDb: any) => {
}
if (!metadata.url) {
const context = {
request: {
body: {
name: metadata.name,
},
},
}
metadata.url = getAppUrl(context)
metadata.url = sdk.applications.getAppUrl({ name: metadata.name })
console.log(`Adding url to app: ${metadata.url}`)
await appDb.put(metadata)
}

View File

@ -0,0 +1,7 @@
import * as sync from "./sync"
import * as utils from "./utils"
export default {
...sync,
...utils,
}

View File

@ -0,0 +1,61 @@
import env from "../../../environment"
import { db as dbCore, context } from "@budibase/backend-core"
import sdk from "../../"
export async function syncApp(
appId: string,
opts?: { automationOnly?: boolean }
) {
if (env.DISABLE_AUTO_PROD_APP_SYNC) {
return {
message:
"App sync disabled. You can reenable with the DISABLE_AUTO_PROD_APP_SYNC environment variable.",
}
}
if (dbCore.isProdAppID(appId)) {
throw new Error("This action cannot be performed for production apps")
}
// replicate prod to dev
const prodAppId = dbCore.getProdAppID(appId)
// specific case, want to make sure setup is skipped
const prodDb = context.getProdAppDB({ skip_setup: true })
const exists = await prodDb.exists()
if (!exists) {
// the database doesn't exist. Don't replicate
return {
message: "App sync not required, app not deployed.",
}
}
const replication = new dbCore.Replication({
source: prodAppId,
target: appId,
})
let error
try {
const replOpts = replication.appReplicateOpts()
if (opts?.automationOnly) {
replOpts.filter = (doc: any) =>
doc._id.startsWith(dbCore.DocumentType.AUTOMATION)
}
await replication.replicate(replOpts)
} catch (err) {
error = err
} finally {
await replication.close()
}
// sync the users
await sdk.users.syncGlobalUsers()
if (error) {
throw error
} else {
return {
message: "App sync completed successfully.",
}
}
}

View File

@ -0,0 +1,17 @@
const URL_REGEX_SLASH = /\/|\\/g
export function getAppUrl(opts?: { name?: string; url?: string }) {
// construct the url
let url
if (opts?.url) {
// if the url is provided, use that
url = encodeURI(opts?.url)
} else if (opts?.name) {
// otherwise use the name
url = encodeURI(`${opts?.name}`)
}
if (url) {
url = `/${url.replace(URL_REGEX_SLASH, "")}`.toLowerCase()
}
return url as string
}

View File

@ -1,15 +1,16 @@
import { default as backups } from "./app/backups"
import { default as tables } from "./app/tables"
import { default as automations } from "./app/automations"
import { default as applications } from "./app/applications"
import { default as users } from "./users"
const sdk = {
backups,
tables,
automations,
applications,
users,
}
// default export for TS
export default sdk
// default export for JS
module.exports = sdk

View File

@ -0,0 +1,5 @@
import * as utils from "./utils"
export default {
...utils,
}

View File

@ -0,0 +1,64 @@
import { getGlobalUsers } from "../../utilities/global"
import { context, roles as rolesCore } from "@budibase/backend-core"
import {
generateUserMetadataID,
getUserMetadataParams,
InternalTables,
} from "../../db/utils"
import { isEqual } from "lodash"
export function combineMetadataAndUser(user: any, metadata: any) {
// skip users with no access
if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
return null
}
delete user._rev
const metadataId = generateUserMetadataID(user._id)
const newDoc = {
...user,
_id: metadataId,
tableId: InternalTables.USER_METADATA,
}
const found = Array.isArray(metadata)
? metadata.find(doc => doc._id === metadataId)
: metadata
// copy rev over for the purposes of equality check
if (found) {
newDoc._rev = found._rev
}
if (found == null || !isEqual(newDoc, found)) {
return {
...found,
...newDoc,
}
}
return null
}
export async function rawUserMetadata() {
const db = context.getAppDB()
return (
await db.allDocs(
getUserMetadataParams(null, {
include_docs: true,
})
)
).rows.map(row => row.doc)
}
export async function syncGlobalUsers() {
// sync user metadata
const db = context.getAppDB()
const [users, metadata] = await Promise.all([
getGlobalUsers(),
rawUserMetadata(),
])
const toWrite = []
for (let user of users) {
const combined = await combineMetadataAndUser(user, metadata)
if (combined) {
toWrite.push(combined)
}
}
await db.bulkDocs(toWrite)
}

View File

@ -3,7 +3,7 @@ import env from "../environment"
import { checkSlashesInUrl } from "./index"
import { db as dbCore, constants, tenancy } from "@budibase/backend-core"
import { updateAppRole } from "./global"
import { BBContext, Automation } from "@budibase/types"
import { BBContext, User } from "@budibase/types"
export function request(ctx?: BBContext, request?: any) {
if (!request.headers) {
@ -138,7 +138,7 @@ export async function deleteGlobalUser(ctx: BBContext) {
return checkResponse(response, "delete user", { ctx })
}
export async function readGlobalUser(ctx: BBContext) {
export async function readGlobalUser(ctx: BBContext): Promise<User> {
const response = await fetch(
checkSlashesInUrl(
env.WORKER_URL + `/api/global/users/${ctx.params.userId}`

View File

@ -189,9 +189,9 @@
integrity sha512-GZt/TCsG70Ms19gfZO1tM4CVnXsPgEPBCpJu+Qz3L0LUDsY5nZqFZglIoPC1kIYOtNBZlrnFT+klg12vFGZXrw==
"@babel/compat-data@^7.20.0":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.1.tgz#f2e6ef7790d8c8dbf03d379502dcc246dcce0b30"
integrity sha512-EWZ4mE2diW3QALKvDMiXnbZpRvlj+nayZ112nK93SnhqOtpdsbVD4W+2tEoT3YNBAG9RBR0ISY758ZkOgsn6pQ==
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733"
integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==
"@babel/core@7.17.4":
version "7.17.4"
@ -236,20 +236,20 @@
semver "^6.3.0"
"@babel/core@^7.11.6":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.2.tgz#8dc9b1620a673f92d3624bd926dc49a52cf25b92"
integrity sha512-w7DbG8DtMrJcFOi4VrLm+8QM4az8Mo+PuLBKLp2zrYRCow8W/f9xiXm5sN53C8HksCyDQwCKha9JiDoIyPjT2g==
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113"
integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.20.2"
"@babel/generator" "^7.20.5"
"@babel/helper-compilation-targets" "^7.20.0"
"@babel/helper-module-transforms" "^7.20.2"
"@babel/helpers" "^7.20.1"
"@babel/parser" "^7.20.2"
"@babel/helpers" "^7.20.5"
"@babel/parser" "^7.20.5"
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.20.1"
"@babel/types" "^7.20.2"
"@babel/traverse" "^7.20.5"
"@babel/types" "^7.20.5"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
@ -265,12 +265,12 @@
"@jridgewell/gen-mapping" "^0.3.0"
jsesc "^2.5.1"
"@babel/generator@^7.20.1", "@babel/generator@^7.20.2":
version "7.20.4"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8"
integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA==
"@babel/generator@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
dependencies:
"@babel/types" "^7.20.2"
"@babel/types" "^7.20.5"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
@ -551,14 +551,14 @@
"@babel/traverse" "^7.18.2"
"@babel/types" "^7.18.2"
"@babel/helpers@^7.20.1":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.1.tgz#2ab7a0fcb0a03b5bf76629196ed63c2d7311f4c9"
integrity sha512-J77mUVaDTUJFZ5BpP6mMn6OIl3rEWymk2ZxDBQJUG3P+PbmyMcF3bYWvz0ma69Af1oobDqT/iAsvzhB58xhQUg==
"@babel/helpers@^7.20.5":
version "7.20.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763"
integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==
dependencies:
"@babel/template" "^7.18.10"
"@babel/traverse" "^7.20.1"
"@babel/types" "^7.20.0"
"@babel/traverse" "^7.20.5"
"@babel/types" "^7.20.5"
"@babel/highlight@^7.16.7":
version "7.17.12"
@ -583,10 +583,10 @@
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
"@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2":
version "7.20.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2"
integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg==
"@babel/parser@^7.18.10", "@babel/parser@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.16.7":
version "7.17.12"
@ -1235,19 +1235,19 @@
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.20.1":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8"
integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA==
"@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.20.1"
"@babel/generator" "^7.20.5"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.20.1"
"@babel/types" "^7.20.0"
"@babel/parser" "^7.20.5"
"@babel/types" "^7.20.5"
debug "^4.1.0"
globals "^11.1.0"
@ -1259,10 +1259,10 @@
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842"
integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==
"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.19.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==
dependencies:
"@babel/helper-string-parser" "^7.19.4"
"@babel/helper-validator-identifier" "^7.19.1"
@ -1273,12 +1273,12 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/backend-core@2.1.40-alpha.7":
version "2.1.40-alpha.7"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.40-alpha.7.tgz#383ce7b679395bcbd5a2dbe52c73915e2a8e456f"
integrity sha512-JWKm47ypnBiLLbdZxPc/Ca08vT14kh2yk3o9f30zH8Uq7O5STu/bbffBVuS/wxgLD8CSKQS6Hbocz3/nADWbIQ==
"@budibase/backend-core@2.1.46-alpha.0":
version "2.1.46-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/backend-core/-/backend-core-2.1.46-alpha.0.tgz#d9063b055cb3c4edb3994a13664c6a070e701a71"
integrity sha512-ulpK2u+5+yIjIyMWhr2cre2ncdZsq5uPt1meNO+kvsetmv37ZnLtmM2gqIe1RNjYkf+mIZlUDA9QDkZowIiTUg==
dependencies:
"@budibase/types" "2.1.40-alpha.7"
"@budibase/types" "2.1.46-alpha.0"
"@shopify/jest-koa-mocks" "5.0.1"
"@techpass/passport-openidconnect" "0.3.2"
aws-sdk "2.1030.0"
@ -1360,13 +1360,13 @@
svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0"
"@budibase/pro@2.1.40-alpha.7":
version "2.1.40-alpha.7"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.40-alpha.7.tgz#697fc833de5efd84c94ed316d010683112394dd1"
integrity sha512-S7UJNZMdN3PebVmGI7MNl41C9jkgJ1dt9F3tIWQsogRA2bJKjaGJzHWWcQ2qFPNNFgMBdYvVk77UQerLS2HfpA==
"@budibase/pro@2.1.46-alpha.0":
version "2.1.46-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/pro/-/pro-2.1.46-alpha.0.tgz#69bf09007edc0f2be275824ba09f27422081c490"
integrity sha512-04iCZVU6BN0afei0wFITgvjRhQ46G/vI51FCBZ9ZiuyTsWBPuAhNqXVAQ/Y48+OVDBMtIvsvJ/S1kCbWUa6x3g==
dependencies:
"@budibase/backend-core" "2.1.40-alpha.7"
"@budibase/types" "2.1.40-alpha.7"
"@budibase/backend-core" "2.1.46-alpha.0"
"@budibase/types" "2.1.46-alpha.0"
"@koa/router" "8.0.8"
bull "4.10.1"
joi "17.6.0"
@ -1390,10 +1390,10 @@
svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0"
"@budibase/types@2.1.40-alpha.7":
version "2.1.40-alpha.7"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.40-alpha.7.tgz#96b01c934d43dd0467ed3ab2b19676a65e97dd68"
integrity sha512-3t4MOHlgHywGiz9qZEd7Zs0PtWgSSVpwNvmEEOe/vSL2xPptFJuT5M90KKgdlkND6vUTJ8wiLA9+OToegk0ndw==
"@budibase/types@2.1.46-alpha.0":
version "2.1.46-alpha.0"
resolved "https://registry.yarnpkg.com/@budibase/types/-/types-2.1.46-alpha.0.tgz#83ecd2c1a26e933fbf27ba6b7a7ef1fed82a8840"
integrity sha512-TTpyax8U0EAsaXqCLACHcfuejZ2x2isyHiM/ia8Ua6R+1rDK9kjYPsiTlJ169/Jq2hv65TzScyaqnHX+g+eJnQ==
"@bull-board/api@3.7.0":
version "3.7.0"
@ -3167,10 +3167,10 @@
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*", "@types/node@>=10.0.0", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
version "18.11.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.13.4", "@types/node@>=13.7.0":
version "17.0.41"
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.41.tgz#1607b2fd3da014ae5d4d1b31bc792a39348dfb9b"
integrity sha512-xA6drNNeqb5YyV5fO3OAEsnXLfO7uF0whiOfPTz5AeDo8KeZFmODKnvwPymMNO8qE/an8pVY/O50tig2SQCrGw==
"@types/node@14.18.20":
version "14.18.20"
@ -3182,6 +3182,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.9.1.tgz#0611b37db4246c937feef529ddcc018cf8e35708"
integrity sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==
"@types/node@>=10.0.0":
version "18.7.18"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.18.tgz#633184f55c322e4fb08612307c274ee6d5ed3154"
integrity sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==
"@types/node@>=8.0.0 <15":
version "14.18.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.21.tgz#0155ee46f6be28b2ff0342ca1a9b9fd4468bef41"
@ -3464,9 +3469,9 @@
"@types/yargs-parser" "*"
"@types/yargs@^17.0.8":
version "17.0.13"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.13.tgz#34cced675ca1b1d51fcf4d34c3c6f0fa142a5c76"
integrity sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==
version "17.0.15"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.15.tgz#5b62c89fb049e2fc8378394a2861a593055f0866"
integrity sha512-ZHc4W2dnEQPfhn06TBEdWaiUHEZAocYaiVMfwOipY5jcJt/251wVrKCBWBetGZWO5CF8tdb7L3DmdxVlZ2BOIg==
dependencies:
"@types/yargs-parser" "*"
@ -4919,9 +4924,9 @@ caniuse-lite@^1.0.30001349:
integrity sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==
caniuse-lite@^1.0.30001400:
version "1.0.30001431"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001431.tgz#e7c59bd1bc518fae03a4656be442ce6c4887a795"
integrity sha512-zBUoFU0ZcxpvSt9IU66dXVT/3ctO1cy4y9cscs1szkPlcWb6pasYM144GqrUygUbT+k7cmUCW61cvskjcv0enQ==
version "1.0.30001435"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001435.tgz#502c93dbd2f493bee73a408fe98e98fb1dad10b2"
integrity sha512-kdCkUTjR+v4YAJelyiDTqiu82BDr4W4CP5sgTA0ZBmqn30XfS2ZghPLMowik9TPhS+psWJiUNxsqLyurDbmutA==
capture-exit@^2.0.0:
version "2.0.0"
@ -5568,9 +5573,9 @@ decamelize@^1.2.0:
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==
version "0.2.2"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^3.3.0:
version "3.3.0"
@ -6073,9 +6078,9 @@ engine.io-parser@~5.0.3:
integrity sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==
engine.io@~6.2.0:
version "6.2.1"
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f"
integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==
version "6.2.0"
resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.2.0.tgz#003bec48f6815926f2b1b17873e576acd54f41d0"
integrity sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==
dependencies:
"@types/cookie" "^0.4.1"
"@types/cors" "^2.8.12"
@ -10049,9 +10054,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
version "2.0.2"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"

Some files were not shown because too many files have changed in this diff Show More